process_bug.cgi 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685
  1. #!/usr/bin/env perl -wT
  2. # -*- Mode: perl; indent-tabs-mode: nil -*-
  3. #
  4. # The contents of this file are subject to the Mozilla Public
  5. # License Version 1.1 (the "License"); you may not use this file
  6. # except in compliance with the License. You may obtain a copy of
  7. # the License at http://www.mozilla.org/MPL/
  8. #
  9. # Software distributed under the License is distributed on an "AS
  10. # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  11. # implied. See the License for the specific language governing
  12. # rights and limitations under the License.
  13. #
  14. # The Original Code is the Bugzilla Bug Tracking System.
  15. #
  16. # The Initial Developer of the Original Code is Netscape Communications
  17. # Corporation. Portions created by Netscape are
  18. # Copyright (C) 1998 Netscape Communications Corporation. All
  19. # Rights Reserved.
  20. #
  21. # Contributor(s): Terry Weissman <terry@mozilla.org>
  22. # Dan Mosedale <dmose@mozilla.org>
  23. # Dave Miller <justdave@syndicomm.com>
  24. # Christopher Aillon <christopher@aillon.com>
  25. # Myk Melez <myk@mozilla.org>
  26. # Jeff Hedlund <jeff.hedlund@matrixsi.com>
  27. # Frédéric Buclin <LpSolit@gmail.com>
  28. # Lance Larsh <lance.larsh@oracle.com>
  29. # Akamai Technologies <bugzilla-dev@akamai.com>
  30. # Max Kanat-Alexander <mkanat@bugzilla.org>
  31. # Implementation notes for this file:
  32. #
  33. # 1) the 'id' form parameter is validated early on, and if it is not a valid
  34. # bugid an error will be reported, so it is OK for later code to simply check
  35. # for a defined form 'id' value, and it can assume a valid bugid.
  36. #
  37. # 2) If the 'id' form parameter is not defined (after the initial validation),
  38. # then we are processing multiple bugs, and @idlist will contain the ids.
  39. #
  40. # 3) If we are processing just the one id, then it is stored in @idlist for
  41. # later processing.
  42. use strict;
  43. use lib qw(. lib);
  44. use Bugzilla;
  45. use Bugzilla::Constants;
  46. use Bugzilla::Bug;
  47. use Bugzilla::BugMail;
  48. use Bugzilla::Mailer;
  49. use Bugzilla::User;
  50. use Bugzilla::Util;
  51. use Bugzilla::Error;
  52. use Bugzilla::Field;
  53. use Bugzilla::Product;
  54. use Bugzilla::Component;
  55. use Bugzilla::Keyword;
  56. use Bugzilla::Flag;
  57. use Bugzilla::Status;
  58. use Bugzilla::Token;
  59. use Storable qw(dclone);
  60. my $user = Bugzilla->login(LOGIN_REQUIRED);
  61. my $cgi = Bugzilla->cgi;
  62. my $dbh = Bugzilla->dbh;
  63. my $template = Bugzilla->template;
  64. my $vars = {};
  65. $vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count();
  66. ######################################################################
  67. # Subroutines
  68. ######################################################################
  69. # Used to send email when an update is done.
  70. sub send_results {
  71. my ($bug_id, $vars) = @_;
  72. my $template = Bugzilla->template;
  73. if (Bugzilla->usage_mode == USAGE_MODE_EMAIL) {
  74. Bugzilla::BugMail::Send($bug_id, $vars->{'mailrecipients'});
  75. }
  76. else {
  77. $template->process("bug/process/results.html.tmpl", $vars)
  78. || ThrowTemplateError($template->error());
  79. }
  80. $vars->{'header_done'} = 1;
  81. }
  82. # Tells us whether or not a field should be changed by process_bug.
  83. sub should_set {
  84. # check_defined is used for fields where there's another field
  85. # whose name starts with "defined_" and then the field name--it's used
  86. # to know when we did things like empty a multi-select or deselect
  87. # a checkbox.
  88. my ($field, $check_defined) = @_;
  89. my $cgi = Bugzilla->cgi;
  90. if ( defined $cgi->param($field)
  91. || ($check_defined && defined $cgi->param("defined_$field")) )
  92. {
  93. return 1;
  94. }
  95. return 0;
  96. }
  97. ######################################################################
  98. # Begin Data/Security Validation
  99. ######################################################################
  100. # Create a list of objects for all bugs being modified in this request.
  101. my @bug_objects;
  102. if (defined $cgi->param('id')) {
  103. my $id = $cgi->param('id');
  104. ValidateBugID($id);
  105. # Store the validated, and detainted id back in the cgi data, as
  106. # lots of later code will need it, and will obtain it from there
  107. $cgi->param('id', $id);
  108. push(@bug_objects, new Bugzilla::Bug($id));
  109. } else {
  110. my @ids;
  111. foreach my $i ($cgi->param()) {
  112. if ($i =~ /^id_([1-9][0-9]*)/) {
  113. my $id = $1;
  114. ValidateBugID($id);
  115. push(@ids, $id);
  116. }
  117. }
  118. @bug_objects = @{Bugzilla::Bug->new_from_list(\@ids)};
  119. }
  120. # Make sure there are bugs to process.
  121. scalar(@bug_objects) || ThrowUserError("no_bugs_chosen", {action => 'modify'});
  122. my $first_bug = $bug_objects[0]; # Used when we're only updating a single bug.
  123. # Delete any parameter set to 'dontchange'.
  124. if (defined $cgi->param('dontchange')) {
  125. foreach my $name ($cgi->param) {
  126. next if $name eq 'dontchange'; # But don't delete dontchange itself!
  127. # Skip ones we've already deleted (such as "defined_$name").
  128. next if !defined $cgi->param($name);
  129. if ($cgi->param($name) eq $cgi->param('dontchange')) {
  130. $cgi->delete($name);
  131. $cgi->delete("defined_$name");
  132. }
  133. }
  134. }
  135. # do a match on the fields if applicable
  136. # The order of these function calls is important, as Flag::validate
  137. # assumes User::match_field has ensured that the values
  138. # in the requestee fields are legitimate user email addresses.
  139. &Bugzilla::User::match_field($cgi, {
  140. 'qa_contact' => { 'type' => 'single' },
  141. 'newcc' => { 'type' => 'multi' },
  142. 'masscc' => { 'type' => 'multi' },
  143. 'assigned_to' => { 'type' => 'single' },
  144. '^requestee(_type)?-(\d+)$' => { 'type' => 'multi' },
  145. });
  146. # Validate flags in all cases. validate() should not detect any
  147. # reference to flags if $cgi->param('id') is undefined.
  148. Bugzilla::Flag::validate($cgi->param('id'));
  149. print $cgi->header() unless Bugzilla->usage_mode == USAGE_MODE_EMAIL;
  150. # Check for a mid-air collision. Currently this only works when updating
  151. # an individual bug.
  152. if (defined $cgi->param('delta_ts')
  153. && $cgi->param('delta_ts') ne $first_bug->delta_ts)
  154. {
  155. ($vars->{'operations'}) =
  156. Bugzilla::Bug::GetBugActivity($first_bug->id, undef,
  157. scalar $cgi->param('delta_ts'));
  158. $vars->{'title_tag'} = "mid_air";
  159. ThrowCodeError('undefined_field', { field => 'longdesclength' })
  160. if !defined $cgi->param('longdesclength');
  161. $vars->{'start_at'} = $cgi->param('longdesclength');
  162. # Always sort midair collision comments oldest to newest,
  163. # regardless of the user's personal preference.
  164. $vars->{'comments'} = Bugzilla::Bug::GetComments($first_bug->id,
  165. "oldest_to_newest");
  166. $vars->{'bug'} = $first_bug;
  167. # The token contains the old delta_ts. We need a new one.
  168. $cgi->param('token', issue_hash_token([$first_bug->id, $first_bug->delta_ts]));
  169. # Warn the user about the mid-air collision and ask them what to do.
  170. $template->process("bug/process/midair.html.tmpl", $vars)
  171. || ThrowTemplateError($template->error());
  172. exit;
  173. }
  174. # We couldn't do this check earlier as we first had to validate bug IDs
  175. # and display the mid-air collision page if delta_ts changed.
  176. # If we do a mass-change, we use session tokens.
  177. my $token = $cgi->param('token');
  178. if ($cgi->param('id')) {
  179. check_hash_token($token, [$first_bug->id, $first_bug->delta_ts]);
  180. }
  181. else {
  182. check_token_data($token, 'buglist_mass_change', 'query.cgi');
  183. }
  184. ######################################################################
  185. # End Data/Security Validation
  186. ######################################################################
  187. $vars->{'title_tag'} = "bug_processed";
  188. # Set up the vars for navigational <link> elements
  189. my @bug_list;
  190. if ($cgi->cookie("BUGLIST")) {
  191. @bug_list = split(/:/, $cgi->cookie("BUGLIST"));
  192. $vars->{'bug_list'} = \@bug_list;
  193. }
  194. my ($action, $next_bug);
  195. if (defined $cgi->param('id')) {
  196. $action = Bugzilla->user->settings->{'post_bug_submit_action'}->{'value'};
  197. if ($action eq 'next_bug') {
  198. my $cur = lsearch(\@bug_list, $cgi->param('id'));
  199. if ($cur >= 0 && $cur < $#bug_list) {
  200. $next_bug = $bug_list[$cur + 1];
  201. # No need to check whether the user can see the bug or not.
  202. # All we want is its ID. An error will be thrown later
  203. # if the user cannot see it.
  204. $vars->{'bug'} = {bug_id => $next_bug};
  205. }
  206. }
  207. # Include both action = 'same_bug' and 'nothing'.
  208. else {
  209. $vars->{'bug'} = {bug_id => $cgi->param('id')};
  210. }
  211. }
  212. else {
  213. # param('id') is not defined when changing multiple bugs at once.
  214. $action = 'nothing';
  215. }
  216. # For each bug, we have to check if the user can edit the bug the product
  217. # is currently in, before we allow them to change anything.
  218. foreach my $bug (@bug_objects) {
  219. if (!Bugzilla->user->can_edit_product($bug->product_obj->id) ) {
  220. ThrowUserError("product_edit_denied",
  221. { product => $bug->product });
  222. }
  223. }
  224. # For security purposes, and because lots of other checks depend on it,
  225. # we set the product first before anything else.
  226. my $product_change; # Used only for strict_isolation checks, right now.
  227. if (should_set('product')) {
  228. foreach my $b (@bug_objects) {
  229. my $changed = $b->set_product(scalar $cgi->param('product'),
  230. { component => scalar $cgi->param('component'),
  231. version => scalar $cgi->param('version'),
  232. target_milestone => scalar $cgi->param('target_milestone'),
  233. change_confirmed => scalar $cgi->param('confirm_product_change'),
  234. other_bugs => \@bug_objects,
  235. });
  236. $product_change ||= $changed;
  237. }
  238. }
  239. # strict_isolation checks mean that we should set the groups
  240. # immediately after changing the product.
  241. foreach my $b (@bug_objects) {
  242. foreach my $group (@{$b->product_obj->groups_valid}) {
  243. my $gid = $group->id;
  244. if (should_set("bit-$gid", 1)) {
  245. # Check ! first to avoid having to check defined below.
  246. if (!$cgi->param("bit-$gid")) {
  247. $b->remove_group($gid);
  248. }
  249. # "== 1" is important because mass-change uses -1 to mean
  250. # "don't change this restriction"
  251. elsif ($cgi->param("bit-$gid") == 1) {
  252. $b->add_group($gid);
  253. }
  254. }
  255. }
  256. }
  257. if ($cgi->param('id') && (defined $cgi->param('dependson')
  258. || defined $cgi->param('blocked')) )
  259. {
  260. $first_bug->set_dependencies(scalar $cgi->param('dependson'),
  261. scalar $cgi->param('blocked'));
  262. }
  263. # Right now, you can't modify dependencies on a mass change.
  264. else {
  265. $cgi->delete('dependson');
  266. $cgi->delete('blocked');
  267. }
  268. my $any_keyword_changes;
  269. if (defined $cgi->param('keywords')) {
  270. foreach my $b (@bug_objects) {
  271. my $return =
  272. $b->modify_keywords(scalar $cgi->param('keywords'),
  273. scalar $cgi->param('keywordaction'));
  274. $any_keyword_changes ||= $return;
  275. }
  276. }
  277. # Component, target_milestone, and version are in here just in case
  278. # the 'product' field wasn't defined in the CGI. It doesn't hurt to set
  279. # them twice.
  280. my @set_fields = qw(op_sys rep_platform priority bug_severity
  281. component target_milestone version
  282. bug_file_loc status_whiteboard short_desc
  283. deadline remaining_time estimated_time);
  284. push(@set_fields, 'assigned_to') if !$cgi->param('set_default_assignee');
  285. push(@set_fields, 'qa_contact') if !$cgi->param('set_default_qa_contact');
  286. my @custom_fields = Bugzilla->active_custom_fields;
  287. my %methods = (
  288. bug_severity => 'set_severity',
  289. rep_platform => 'set_platform',
  290. short_desc => 'set_summary',
  291. bug_file_loc => 'set_url',
  292. );
  293. foreach my $b (@bug_objects) {
  294. if (should_set('comment') || $cgi->param('work_time')) {
  295. # Add a comment as needed to each bug. This is done early because
  296. # there are lots of things that want to check if we added a comment.
  297. $b->add_comment(scalar($cgi->param('comment')),
  298. { isprivate => scalar $cgi->param('commentprivacy'),
  299. work_time => scalar $cgi->param('work_time') });
  300. }
  301. foreach my $field_name (@set_fields) {
  302. if (should_set($field_name)) {
  303. my $method = $methods{$field_name};
  304. $method ||= "set_" . $field_name;
  305. $b->$method($cgi->param($field_name));
  306. }
  307. }
  308. $b->reset_assigned_to if $cgi->param('set_default_assignee');
  309. $b->reset_qa_contact if $cgi->param('set_default_qa_contact');
  310. # And set custom fields.
  311. foreach my $field (@custom_fields) {
  312. my $fname = $field->name;
  313. if (should_set($fname, 1)) {
  314. $b->set_custom_field($field, [$cgi->param($fname)]);
  315. }
  316. }
  317. }
  318. # Certain changes can only happen on individual bugs, never on mass-changes.
  319. if (defined $cgi->param('id')) {
  320. # Since aliases are unique (like bug numbers), they can only be changed
  321. # for one bug at a time.
  322. if (Bugzilla->params->{"usebugaliases"} && defined $cgi->param('alias')) {
  323. $first_bug->set_alias($cgi->param('alias'));
  324. }
  325. # reporter_accessible and cclist_accessible--these are only set if
  326. # the user can change them and they appear on the page.
  327. if (should_set('cclist_accessible', 1)) {
  328. $first_bug->set_cclist_accessible($cgi->param('cclist_accessible'))
  329. }
  330. if (should_set('reporter_accessible', 1)) {
  331. $first_bug->set_reporter_accessible($cgi->param('reporter_accessible'))
  332. }
  333. # You can only mark/unmark comments as private on single bugs. If
  334. # you're not in the insider group, this code won't do anything.
  335. foreach my $field (grep(/^defined_isprivate/, $cgi->param())) {
  336. $field =~ /(\d+)$/;
  337. my $comment_id = $1;
  338. $first_bug->set_comment_is_private($comment_id,
  339. $cgi->param("isprivate_$comment_id"));
  340. }
  341. }
  342. # We need to check the addresses involved in a CC change before we touch
  343. # any bugs. What we'll do here is formulate the CC data into two arrays of
  344. # users involved in this CC change. Then those arrays can be used later
  345. # on for the actual change.
  346. my (@cc_add, @cc_remove);
  347. if (defined $cgi->param('newcc')
  348. || defined $cgi->param('addselfcc')
  349. || defined $cgi->param('removecc')
  350. || defined $cgi->param('masscc')) {
  351. # If masscc is defined, then we came from buglist and need to either add or
  352. # remove cc's... otherwise, we came from bugform and may need to do both.
  353. my ($cc_add, $cc_remove) = "";
  354. if (defined $cgi->param('masscc')) {
  355. if ($cgi->param('ccaction') eq 'add') {
  356. $cc_add = join(' ',$cgi->param('masscc'));
  357. } elsif ($cgi->param('ccaction') eq 'remove') {
  358. $cc_remove = join(' ',$cgi->param('masscc'));
  359. }
  360. } else {
  361. $cc_add = join(' ',$cgi->param('newcc'));
  362. # We came from bug_form which uses a select box to determine what cc's
  363. # need to be removed...
  364. if (defined $cgi->param('removecc') && $cgi->param('cc')) {
  365. $cc_remove = join (",", $cgi->param('cc'));
  366. }
  367. }
  368. push(@cc_add, split(/[\s,]+/, $cc_add)) if $cc_add;
  369. push(@cc_add, Bugzilla->user) if $cgi->param('addselfcc');
  370. push(@cc_remove, split(/[\s,]+/, $cc_remove)) if $cc_remove;
  371. }
  372. foreach my $b (@bug_objects) {
  373. $b->remove_cc($_) foreach @cc_remove;
  374. $b->add_cc($_) foreach @cc_add;
  375. # Theoretically you could move a product without ever specifying
  376. # a new assignee or qa_contact, or adding/removing any CCs. So,
  377. # we have to check that the current assignee, qa, and CCs are still
  378. # valid if we've switched products, under strict_isolation. We can only
  379. # do that here. There ought to be some better way to do this,
  380. # architecturally, but I haven't come up with it.
  381. if ($product_change) {
  382. $b->_check_strict_isolation();
  383. }
  384. }
  385. my $move_action = $cgi->param('action') || '';
  386. if ($move_action eq Bugzilla->params->{'move-button-text'}) {
  387. Bugzilla->params->{'move-enabled'} || ThrowUserError("move_bugs_disabled");
  388. $user->is_mover || ThrowUserError("auth_failure", {action => 'move',
  389. object => 'bugs'});
  390. $dbh->bz_start_transaction();
  391. # First update all moved bugs.
  392. foreach my $bug (@bug_objects) {
  393. $bug->add_comment('', { type => CMT_MOVED_TO, extra_data => $user->login });
  394. }
  395. # Don't export the new status and resolution. We want the current ones.
  396. local $Storable::forgive_me = 1;
  397. my $bugs = dclone(\@bug_objects);
  398. my $new_status = Bugzilla->params->{'duplicate_or_move_bug_status'};
  399. foreach my $bug (@bug_objects) {
  400. $bug->set_status($new_status, {resolution => 'MOVED', moving => 1});
  401. }
  402. $_->update() foreach @bug_objects;
  403. $dbh->bz_commit_transaction();
  404. # Now send emails.
  405. foreach my $bug (@bug_objects) {
  406. $vars->{'mailrecipients'} = { 'changer' => $user->login };
  407. $vars->{'id'} = $bug->id;
  408. $vars->{'type'} = "move";
  409. send_results($bug->id, $vars);
  410. }
  411. # Prepare and send all data about these bugs to the new database
  412. my $to = Bugzilla->params->{'move-to-address'};
  413. $to =~ s/@/\@/;
  414. my $from = Bugzilla->params->{'moved-from-address'};
  415. $from =~ s/@/\@/;
  416. my $msg = "To: $to\n";
  417. $msg .= "From: Bugzilla <" . $from . ">\n";
  418. $msg .= "Subject: Moving bug(s) " . join(', ', map($_->id, @bug_objects))
  419. . "\n\n";
  420. my @fieldlist = (Bugzilla::Bug->fields, 'group', 'long_desc',
  421. 'attachment', 'attachmentdata');
  422. my %displayfields;
  423. foreach (@fieldlist) {
  424. $displayfields{$_} = 1;
  425. }
  426. $template->process("bug/show.xml.tmpl", { bugs => $bugs,
  427. displayfields => \%displayfields,
  428. }, \$msg)
  429. || ThrowTemplateError($template->error());
  430. $msg .= "\n";
  431. MessageToMTA($msg);
  432. # End the response page.
  433. unless (Bugzilla->usage_mode == USAGE_MODE_EMAIL) {
  434. $template->process("bug/navigate.html.tmpl", $vars)
  435. || ThrowTemplateError($template->error());
  436. $template->process("global/footer.html.tmpl", $vars)
  437. || ThrowTemplateError($template->error());
  438. }
  439. exit;
  440. }
  441. # You cannot mark bugs as duplicates when changing several bugs at once
  442. # (because currently there is no way to check for duplicate loops in that
  443. # situation).
  444. if (!$cgi->param('id') && $cgi->param('dup_id')) {
  445. ThrowUserError('dupe_not_allowed');
  446. }
  447. # Set the status, resolution, and dupe_of (if needed). This has to be done
  448. # down here, because the validity of status changes depends on other fields,
  449. # such as Target Milestone.
  450. foreach my $b (@bug_objects) {
  451. if (should_set('bug_status')) {
  452. $b->set_status(
  453. scalar $cgi->param('bug_status'),
  454. {resolution => scalar $cgi->param('resolution'),
  455. dupe_of => scalar $cgi->param('dup_id')}
  456. );
  457. }
  458. elsif (should_set('resolution')) {
  459. $b->set_resolution(scalar $cgi->param('resolution'),
  460. {dupe_of => scalar $cgi->param('dup_id')});
  461. }
  462. elsif (should_set('dup_id')) {
  463. $b->set_dup_id(scalar $cgi->param('dup_id'));
  464. }
  465. }
  466. ##############################
  467. # Do Actual Database Updates #
  468. ##############################
  469. foreach my $bug (@bug_objects) {
  470. $dbh->bz_start_transaction();
  471. my $timestamp = $dbh->selectrow_array(q{SELECT NOW()});
  472. my $changes = $bug->update($timestamp);
  473. my %notify_deps;
  474. if ($changes->{'bug_status'}) {
  475. my ($old_status, $new_status) = @{ $changes->{'bug_status'} };
  476. # If this bug has changed from opened to closed or vice-versa,
  477. # then all of the bugs we block need to be notified.
  478. if (is_open_state($old_status) ne is_open_state($new_status)) {
  479. $notify_deps{$_} = 1 foreach (@{$bug->blocked});
  480. }
  481. # We may have zeroed the remaining time, if we moved into a closed
  482. # status, so we should inform the user about that.
  483. if (!is_open_state($new_status) && $changes->{'remaining_time'}) {
  484. $vars->{'message'} = "remaining_time_zeroed"
  485. if Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'});
  486. }
  487. }
  488. # To get a list of all changed dependencies, convert the "changes" arrays
  489. # into a long string, then collapse that string into unique numbers in
  490. # a hash.
  491. my $all_changed_deps = join(', ', @{ $changes->{'dependson'} || [] });
  492. $all_changed_deps = join(', ', @{ $changes->{'blocked'} || [] },
  493. $all_changed_deps);
  494. my %changed_deps = map { $_ => 1 } split(', ', $all_changed_deps);
  495. # When clearning one field (say, blocks) and filling in the other
  496. # (say, dependson), an empty string can get into the hash and cause
  497. # an error later.
  498. delete $changed_deps{''};
  499. # $msgs will store emails which have to be sent to voters, if any.
  500. my $msgs;
  501. if ($changes->{'product'}) {
  502. # If some votes have been removed, RemoveVotes() returns
  503. # a list of messages to send to voters.
  504. # We delay the sending of these messages till tables are unlocked.
  505. $msgs = RemoveVotes($bug->id, 0, 'votes_bug_moved');
  506. CheckIfVotedConfirmed($bug->id, Bugzilla->user->id);
  507. }
  508. # Set and update flags.
  509. Bugzilla::Flag->process($bug, undef, $timestamp, $vars);
  510. $dbh->bz_commit_transaction();
  511. ###############
  512. # Send Emails #
  513. ###############
  514. # Now is a good time to send email to voters.
  515. foreach my $msg (@$msgs) {
  516. MessageToMTA($msg);
  517. }
  518. my $old_qa = $changes->{'qa_contact'} ? $changes->{'qa_contact'}->[0] : '';
  519. my $old_own = $changes->{'assigned_to'} ? $changes->{'assigned_to'}->[0] : '';
  520. my $old_cc = $changes->{cc} ? $changes->{cc}->[0] : '';
  521. $vars->{'mailrecipients'} = {
  522. cc => [split(/[\s,]+/, $old_cc)],
  523. owner => $old_own,
  524. qacontact => $old_qa,
  525. changer => Bugzilla->user->login };
  526. $vars->{'id'} = $bug->id;
  527. $vars->{'type'} = "bug";
  528. # Let the user know the bug was changed and who did and didn't
  529. # receive email about the change.
  530. send_results($bug->id, $vars);
  531. # If the bug was marked as a duplicate, we need to notify users on the
  532. # other bug of any changes to that bug.
  533. my $new_dup_id = $changes->{'dup_id'} ? $changes->{'dup_id'}->[1] : undef;
  534. if ($new_dup_id) {
  535. $vars->{'mailrecipients'} = { 'changer' => Bugzilla->user->login };
  536. $vars->{'id'} = $new_dup_id;
  537. $vars->{'type'} = "dupe";
  538. # Let the user know a duplication notation was added to the
  539. # original bug.
  540. send_results($new_dup_id, $vars);
  541. }
  542. my %all_dep_changes = (%notify_deps, %changed_deps);
  543. foreach my $id (sort { $a <=> $b } (keys %all_dep_changes)) {
  544. $vars->{'mailrecipients'} = { 'changer' => Bugzilla->user->login };
  545. $vars->{'id'} = $id;
  546. $vars->{'type'} = "dep";
  547. # Let the user (if he is able to see the bug) know we checked to
  548. # see if we should email notice of this change to users with a
  549. # relationship to the dependent bug and who did and didn't
  550. # receive email about it.
  551. send_results($id, $vars);
  552. }
  553. }
  554. # Determine if Patch Viewer is installed, for Diff link
  555. # (NB: Duplicate code with show_bug.cgi.)
  556. eval {
  557. require PatchReader;
  558. $vars->{'patchviewerinstalled'} = 1;
  559. };
  560. if (Bugzilla->usage_mode == USAGE_MODE_EMAIL) {
  561. # Do nothing.
  562. }
  563. elsif ($action eq 'next_bug') {
  564. if ($next_bug) {
  565. if (detaint_natural($next_bug) && Bugzilla->user->can_see_bug($next_bug)) {
  566. my $bug = new Bugzilla::Bug($next_bug);
  567. ThrowCodeError("bug_error", { bug => $bug }) if $bug->error;
  568. $vars->{'bugs'} = [$bug];
  569. $vars->{'nextbug'} = $bug->bug_id;
  570. $template->process("bug/show.html.tmpl", $vars)
  571. || ThrowTemplateError($template->error());
  572. exit;
  573. }
  574. }
  575. } elsif ($action eq 'same_bug') {
  576. if (Bugzilla->user->can_see_bug($cgi->param('id'))) {
  577. my $bug = new Bugzilla::Bug($cgi->param('id'));
  578. ThrowCodeError("bug_error", { bug => $bug }) if $bug->error;
  579. $vars->{'bugs'} = [$bug];
  580. $template->process("bug/show.html.tmpl", $vars)
  581. || ThrowTemplateError($template->error());
  582. exit;
  583. }
  584. } elsif ($action ne 'nothing') {
  585. ThrowCodeError("invalid_post_bug_submit_action");
  586. }
  587. # End the response page.
  588. unless (Bugzilla->usage_mode == USAGE_MODE_EMAIL) {
  589. $template->process("bug/navigate.html.tmpl", $vars)
  590. || ThrowTemplateError($template->error());
  591. $template->process("global/footer.html.tmpl", $vars)
  592. || ThrowTemplateError($template->error());
  593. }
  594. 1;