bzr branch
/bugzilla/trunk
| Line | Revision | Contents |
| 1 | 2107 | #!/usr/bin/perl -wT |
| 2 | 8075 | # This Source Code Form is subject to the terms of the Mozilla Public |
| 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
|
| 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
| 5 | # |
|
| 6 | # This Source Code Form is "Incompatible With Secondary Licenses", as |
|
| 7 | # defined by the Mozilla Public License, v. 2.0. |
|
| 8 | 1 | |
| 9 | 3316 | # Implementation notes for this file: |
| 10 | # |
|
| 11 | # 1) the 'id' form parameter is validated early on, and if it is not a valid |
|
| 12 | # bugid an error will be reported, so it is OK for later code to simply check |
|
| 13 | # for a defined form 'id' value, and it can assume a valid bugid. |
|
| 14 | # |
|
| 15 | # 2) If the 'id' form parameter is not defined (after the initial validation), |
|
| 16 | # then we are processing multiple bugs, and @idlist will contain the ids. |
|
| 17 | # |
|
| 18 | # 3) If we are processing just the one id, then it is stored in @idlist for |
|
| 19 | # later processing. |
|
| 20 | ||
| 21 | 47 | use strict; |
| 22 | ||
| 23 | 5304 | use lib qw(. lib); |
| 24 | 1243 | |
| 25 | 2161 | use Bugzilla; |
| 26 | 1957 | use Bugzilla::Constants; |
| 27 | 2493 | use Bugzilla::Bug; |
| 28 | 1830 | use Bugzilla::User; |
| 29 | 2994 | use Bugzilla::Util; |
| 30 | 4296 | use Bugzilla::Error; |
| 31 | 3537 | use Bugzilla::Field; |
| 32 | 4048 | use Bugzilla::Product; |
| 33 | 4180 | use Bugzilla::Component; |
| 34 | 4078 | use Bugzilla::Keyword; |
| 35 | 1771 | use Bugzilla::Flag; |
| 36 | 5285 | use Bugzilla::Status; |
| 37 | 6446 | use Bugzilla::Token; |
| 38 | 1771 | |
| 39 | 7138 | use List::MoreUtils qw(firstidx); |
| 40 | 5101 | use Storable qw(dclone); |
| 41 | ||
| 42 | 2537 | my $user = Bugzilla->login(LOGIN_REQUIRED); |
| 43 | 47 | |
| 44 | 2161 | my $cgi = Bugzilla->cgi; |
| 45 | 3109 | my $dbh = Bugzilla->dbh; |
| 46 | 3762 | my $template = Bugzilla->template; |
| 47 | 5418 | my $vars = {}; |
| 48 | 2161 | |
| 49 | 899 | ###################################################################### |
| 50 | 4039 | # Subroutines |
| 51 | ###################################################################### |
|
| 52 | ||
| 53 | 5459 | # Tells us whether or not a field should be changed by process_bug. |
| 54 | 5255 | sub should_set { |
| 55 | 5398 | # check_defined is used for fields where there's another field |
| 56 | 5256 | # whose name starts with "defined_" and then the field name--it's used |
| 57 | # to know when we did things like empty a multi-select or deselect |
|
| 58 | # a checkbox. |
|
| 59 | my ($field, $check_defined) = @_; |
|
| 60 | 5255 | my $cgi = Bugzilla->cgi; |
| 61 | 5418 | if ( defined $cgi->param($field) |
| 62 | || ($check_defined && defined $cgi->param("defined_$field")) ) |
|
| 63 | 5255 | { |
| 64 | return 1; |
|
| 65 | } |
|
| 66 | return 0; |
|
| 67 | } |
|
| 68 | ||
| 69 | 4039 | ###################################################################### |
| 70 | 899 | # Begin Data/Security Validation |
| 71 | ###################################################################### |
|
| 72 | ||
| 73 | 5418 | # Create a list of objects for all bugs being modified in this request. |
| 74 | my @bug_objects; |
|
| 75 | 3316 | if (defined $cgi->param('id')) { |
| 76 | 7949 | my $bug = Bugzilla::Bug->check_for_edit(scalar $cgi->param('id')); |
| 77 | 6113 | $cgi->param('id', $bug->id); |
| 78 | push(@bug_objects, $bug); |
|
| 79 | 899 | } else { |
| 80 | 3316 | foreach my $i ($cgi->param()) { |
| 81 | 912 | if ($i =~ /^id_([1-9][0-9]*)/) { |
| 82 | 1243 | my $id = $1; |
| 83 | 7949 | push(@bug_objects, Bugzilla::Bug->check_for_edit($id)); |
| 84 | 912 | } |
| 85 | 899 | } |
| 86 | } |
|
| 87 | ||
| 88 | 1060 | # Make sure there are bugs to process. |
| 89 | 5418 | scalar(@bug_objects) || ThrowUserError("no_bugs_chosen", {action => 'modify'}); |
| 90 | ||
| 91 | my $first_bug = $bug_objects[0]; # Used when we're only updating a single bug. |
|
| 92 | ||
| 93 | # Delete any parameter set to 'dontchange'. |
|
| 94 | if (defined $cgi->param('dontchange')) { |
|
| 95 | foreach my $name ($cgi->param) { |
|
| 96 | next if $name eq 'dontchange'; # But don't delete dontchange itself! |
|
| 97 | 5459 | # Skip ones we've already deleted (such as "defined_$name"). |
| 98 | next if !defined $cgi->param($name); |
|
| 99 | 5418 | if ($cgi->param($name) eq $cgi->param('dontchange')) { |
| 100 | $cgi->delete($name); |
|
| 101 | 5459 | $cgi->delete("defined_$name"); |
| 102 | 5418 | } |
| 103 | } |
|
| 104 | 2840 | } |
| 105 | ||
| 106 | 1830 | # do a match on the fields if applicable |
| 107 | 6943 | Bugzilla::User::match_field({ |
| 108 | 1915 | 'qa_contact' => { 'type' => 'single' }, |
| 109 | 'newcc' => { 'type' => 'multi' }, |
|
| 110 | 2293 | 'masscc' => { 'type' => 'multi' }, |
| 111 | 1915 | 'assigned_to' => { 'type' => 'single' }, |
| 112 | 1830 | }); |
| 113 | 3486 | |
| 114 | 4621 | print $cgi->header() unless Bugzilla->usage_mode == USAGE_MODE_EMAIL; |
| 115 | 5418 | |
| 116 | # Check for a mid-air collision. Currently this only works when updating |
|
| 117 | # an individual bug. |
|
| 118 | 7105 | if (defined $cgi->param('delta_ts')) |
| 119 | 5418 | { |
| 120 | 7105 | my $delta_ts_z = datetime_from($cgi->param('delta_ts')); |
| 121 | my $first_delta_tz_z = datetime_from($first_bug->delta_ts); |
|
| 122 | if ($first_delta_tz_z ne $delta_ts_z) { |
|
| 123 | 8043 | ($vars->{'operations'}) = $first_bug->get_activity(undef, $cgi->param('delta_ts')); |
| 124 | 7105 | |
| 125 | $vars->{'title_tag'} = "mid_air"; |
|
| 126 | ||
| 127 | ThrowCodeError('undefined_field', { field => 'longdesclength' }) |
|
| 128 | if !defined $cgi->param('longdesclength'); |
|
| 129 | ||
| 130 | $vars->{'start_at'} = $cgi->param('longdesclength'); |
|
| 131 | # Always sort midair collision comments oldest to newest, |
|
| 132 | # regardless of the user's personal preference. |
|
| 133 | $vars->{'comments'} = $first_bug->comments({ order => "oldest_to_newest" }); |
|
| 134 | $vars->{'bug'} = $first_bug; |
|
| 135 | ||
| 136 | # The token contains the old delta_ts. We need a new one. |
|
| 137 | $cgi->param('token', issue_hash_token([$first_bug->id, $first_bug->delta_ts])); |
|
| 138 | # Warn the user about the mid-air collision and ask them what to do. |
|
| 139 | $template->process("bug/process/midair.html.tmpl", $vars) |
|
| 140 | || ThrowTemplateError($template->error()); |
|
| 141 | exit; |
|
| 142 | } |
|
| 143 | 5418 | } |
| 144 | ||
| 145 | 6446 | # We couldn't do this check earlier as we first had to validate bug IDs |
| 146 | # and display the mid-air collision page if delta_ts changed. |
|
| 147 | # If we do a mass-change, we use session tokens. |
|
| 148 | my $token = $cgi->param('token'); |
|
| 149 | ||
| 150 | if ($cgi->param('id')) { |
|
| 151 | check_hash_token($token, [$first_bug->id, $first_bug->delta_ts]); |
|
| 152 | } |
|
| 153 | else { |
|
| 154 | check_token_data($token, 'buglist_mass_change', 'query.cgi'); |
|
| 155 | } |
|
| 156 | ||
| 157 | ###################################################################### |
|
| 158 | # End Data/Security Validation |
|
| 159 | ###################################################################### |
|
| 160 | ||
| 161 | 1811 | $vars->{'title_tag'} = "bug_processed"; |
| 162 | 1761 | |
| 163 | 6884 | my $action; |
| 164 | 5465 | if (defined $cgi->param('id')) { |
| 165 | 7865 | $action = $user->setting('post_bug_submit_action'); |
| 166 | 5465 | |
| 167 | if ($action eq 'next_bug') { |
|
| 168 | 7379 | my $bug_list_obj = $user->recent_search_for($first_bug); |
| 169 | my @bug_list = $bug_list_obj ? @{$bug_list_obj->bug_list} : (); |
|
| 170 | 7138 | my $cur = firstidx { $_ eq $cgi->param('id') } @bug_list; |
| 171 | 5465 | if ($cur >= 0 && $cur < $#bug_list) { |
| 172 | 6884 | my $next_bug_id = $bug_list[$cur + 1]; |
| 173 | detaint_natural($next_bug_id); |
|
| 174 | if ($next_bug_id and $user->can_see_bug($next_bug_id)) { |
|
| 175 | 7214 | # We create an object here so that $bug->send_changes can use it |
| 176 | 6884 | # when displaying the header. |
| 177 | $vars->{'bug'} = new Bugzilla::Bug($next_bug_id); |
|
| 178 | } |
|
| 179 | 5465 | } |
| 180 | } |
|
| 181 | # Include both action = 'same_bug' and 'nothing'. |
|
| 182 | else { |
|
| 183 | 6884 | $vars->{'bug'} = $first_bug; |
| 184 | 5465 | } |
| 185 | } |
|
| 186 | else { |
|
| 187 | # param('id') is not defined when changing multiple bugs at once. |
|
| 188 | $action = 'nothing'; |
|
| 189 | } |
|
| 190 | ||
| 191 | 5404 | # Component, target_milestone, and version are in here just in case |
| 192 | # the 'product' field wasn't defined in the CGI. It doesn't hurt to set |
|
| 193 | # them twice. |
|
| 194 | 5372 | my @set_fields = qw(op_sys rep_platform priority bug_severity |
| 195 | component target_milestone version |
|
| 196 | bug_file_loc status_whiteboard short_desc |
|
| 197 | 7158 | deadline remaining_time estimated_time |
| 198 | 7174 | work_time set_default_assignee set_default_qa_contact |
| 199 | 7193 | cclist_accessible reporter_accessible |
| 200 | 7194 | product confirm_product_change |
| 201 | bug_status resolution dup_id); |
|
| 202 | 5372 | push(@set_fields, 'assigned_to') if !$cgi->param('set_default_assignee'); |
| 203 | push(@set_fields, 'qa_contact') if !$cgi->param('set_default_qa_contact'); |
|
| 204 | 7157 | my %field_translation = ( |
| 205 | bug_severity => 'severity', |
|
| 206 | rep_platform => 'platform', |
|
| 207 | short_desc => 'summary', |
|
| 208 | bug_file_loc => 'url', |
|
| 209 | 7173 | set_default_assignee => 'reset_assigned_to', |
| 210 | set_default_qa_contact => 'reset_qa_contact', |
|
| 211 | 7193 | confirm_product_change => 'product_change_confirmed', |
| 212 | 7157 | ); |
| 213 | ||
| 214 | 7193 | my %set_all_fields = ( other_bugs => \@bug_objects ); |
| 215 | 7157 | foreach my $field_name (@set_fields) { |
| 216 | 7191 | if (should_set($field_name, 1)) { |
| 217 | 7157 | my $param_name = $field_translation{$field_name} || $field_name; |
| 218 | $set_all_fields{$param_name} = $cgi->param($field_name); |
|
| 219 | } |
|
| 220 | } |
|
| 221 | ||
| 222 | 7236 | if (should_set('keywords')) { |
| 223 | 7273 | my $action = $cgi->param('keywordaction') || ''; |
| 224 | # Backward-compatibility for Bugzilla 3.x and older. |
|
| 225 | 7236 | $action = 'remove' if $action eq 'delete'; |
| 226 | $action = 'set' if $action eq 'makeexact'; |
|
| 227 | $set_all_fields{keywords}->{$action} = $cgi->param('keywords'); |
|
| 228 | } |
|
| 229 | 7158 | if (should_set('comment')) { |
| 230 | $set_all_fields{comment} = { |
|
| 231 | body => scalar $cgi->param('comment'), |
|
| 232 | 7555 | is_private => scalar $cgi->param('comment_is_private'), |
| 233 | 7158 | }; |
| 234 | } |
|
| 235 | 7173 | if (should_set('see_also')) { |
| 236 | $set_all_fields{'see_also'}->{add} = |
|
| 237 | [split(/[\s,]+/, $cgi->param('see_also'))]; |
|
| 238 | } |
|
| 239 | if (should_set('remove_see_also')) { |
|
| 240 | $set_all_fields{'see_also'}->{remove} = [$cgi->param('remove_see_also')]; |
|
| 241 | } |
|
| 242 | 7175 | foreach my $dep_field (qw(dependson blocked)) { |
| 243 | if (should_set($dep_field)) { |
|
| 244 | if (my $dep_action = $cgi->param("${dep_field}_action")) { |
|
| 245 | $set_all_fields{$dep_field}->{$dep_action} = |
|
| 246 | [split(/\s,/, $cgi->param($dep_field))]; |
|
| 247 | } |
|
| 248 | else { |
|
| 249 | $set_all_fields{$dep_field}->{set} = $cgi->param($dep_field); |
|
| 250 | } |
|
| 251 | } |
|
| 252 | } |
|
| 253 | 7192 | # Formulate the CC data into two arrays of users involved in this CC change. |
| 254 | if (defined $cgi->param('newcc') |
|
| 255 | or defined $cgi->param('addselfcc') |
|
| 256 | or defined $cgi->param('removecc') |
|
| 257 | or defined $cgi->param('masscc')) |
|
| 258 | { |
|
| 259 | 7457 | my (@cc_add, @cc_remove); |
| 260 | 7192 | # If masscc is defined, then we came from buglist and need to either add or |
| 261 | # remove cc's... otherwise, we came from show_bug and may need to do both. |
|
| 262 | if (defined $cgi->param('masscc')) { |
|
| 263 | if ($cgi->param('ccaction') eq 'add') { |
|
| 264 | 7457 | @cc_add = $cgi->param('masscc'); |
| 265 | 7192 | } elsif ($cgi->param('ccaction') eq 'remove') { |
| 266 | 7457 | @cc_remove = $cgi->param('masscc'); |
| 267 | 7192 | } |
| 268 | } else { |
|
| 269 | 7457 | @cc_add = $cgi->param('newcc'); |
| 270 | 7930 | push(@cc_add, $user) if $cgi->param('addselfcc'); |
| 271 | 7457 | |
| 272 | # We came from show_bug which uses a select box to determine what cc's |
|
| 273 | 7192 | # need to be removed... |
| 274 | if ($cgi->param('removecc') && $cgi->param('cc')) { |
|
| 275 | 7457 | @cc_remove = $cgi->param('cc'); |
| 276 | 7192 | } |
| 277 | } |
|
| 278 | ||
| 279 | 7457 | $set_all_fields{cc} = { add => \@cc_add, remove => \@cc_remove }; |
| 280 | 7192 | } |
| 281 | 7191 | |
| 282 | # Fields that can only be set on one bug at a time. |
|
| 283 | if (defined $cgi->param('id')) { |
|
| 284 | # Since aliases are unique (like bug numbers), they can only be changed |
|
| 285 | # for one bug at a time. |
|
| 286 | 8049 | if (defined $cgi->param('alias')) { |
| 287 | 7191 | $set_all_fields{alias} = $cgi->param('alias'); |
| 288 | } |
|
| 289 | } |
|
| 290 | ||
| 291 | my %is_private; |
|
| 292 | foreach my $field (grep(/^defined_isprivate/, $cgi->param())) { |
|
| 293 | $field =~ /(\d+)$/; |
|
| 294 | my $comment_id = $1; |
|
| 295 | $is_private{$comment_id} = $cgi->param("isprivate_$comment_id"); |
|
| 296 | } |
|
| 297 | $set_all_fields{comment_is_private} = \%is_private; |
|
| 298 | ||
| 299 | 7250 | my @check_groups = $cgi->param('defined_groups'); |
| 300 | my @set_groups = $cgi->param('groups'); |
|
| 301 | my ($removed_groups) = diff_arrays(\@check_groups, \@set_groups); |
|
| 302 | $set_all_fields{groups} = { add => \@set_groups, remove => $removed_groups }; |
|
| 303 | 7158 | |
| 304 | 5493 | my @custom_fields = Bugzilla->active_custom_fields; |
| 305 | 7173 | foreach my $field (@custom_fields) { |
| 306 | my $fname = $field->name; |
|
| 307 | if (should_set($fname, 1)) { |
|
| 308 | $set_all_fields{$fname} = [$cgi->param($fname)]; |
|
| 309 | } |
|
| 310 | } |
|
| 311 | 5372 | |
| 312 | 7888 | # We are going to alter the list of removed groups, so we keep a copy here. |
| 313 | my @unchecked_groups = @$removed_groups; |
|
| 314 | 5110 | foreach my $b (@bug_objects) { |
| 315 | 7888 | # Don't blindly ask to remove unchecked groups available in the UI. |
| 316 | # A group can be already unchecked, and the user didn't try to remove it. |
|
| 317 | # In this case, we don't want remove_group() to complain. |
|
| 318 | my @remove_groups; |
|
| 319 | foreach my $g (@{$b->groups_in}) { |
|
| 320 | push(@remove_groups, $g->name) if grep { $_ eq $g->name } @unchecked_groups; |
|
| 321 | } |
|
| 322 | local $set_all_fields{groups}->{remove} = \@remove_groups; |
|
| 323 | 7157 | $b->set_all(\%set_all_fields); |
| 324 | 5418 | } |
| 325 | 5021 | |
| 326 | 3316 | if (defined $cgi->param('id')) { |
| 327 | 7178 | # Flags should be set AFTER the bug has been moved into another |
| 328 | 7191 | # product/component. The structure of flags code doesn't currently |
| 329 | # allow them to be set using set_all. |
|
| 330 | 7178 | my ($flags, $new_flags) = Bugzilla::Flag->extract_flags_from_cgi( |
| 331 | $first_bug, undef, $vars); |
|
| 332 | $first_bug->set_flags($flags, $new_flags); |
|
| 333 | 1702 | } |
| 334 | 983 | |
| 335 | 5418 | ############################## |
| 336 | # Do Actual Database Updates # |
|
| 337 | ############################## |
|
| 338 | foreach my $bug (@bug_objects) { |
|
| 339 | 7214 | my $changes = $bug->update(); |
| 340 | ||
| 341 | 5404 | if ($changes->{'bug_status'}) { |
| 342 | 7214 | my $new_status = $changes->{'bug_status'}->[1]; |
| 343 | 5404 | # We may have zeroed the remaining time, if we moved into a closed |
| 344 | # status, so we should inform the user about that. |
|
| 345 | if (!is_open_state($new_status) && $changes->{'remaining_time'}) { |
|
| 346 | $vars->{'message'} = "remaining_time_zeroed" |
|
| 347 | 7930 | if $user->is_timetracker; |
| 348 | 5404 | } |
| 349 | } |
|
| 350 | 2735 | |
| 351 | 7214 | $bug->send_changes($changes, $vars); |
| 352 | 1 | } |
| 353 | ||
| 354 | 8011 | # Delete the session token used for the mass-change. |
| 355 | delete_token($token) unless $cgi->param('id'); |
|
| 356 | ||
| 357 | 4621 | if (Bugzilla->usage_mode == USAGE_MODE_EMAIL) { |
| 358 | # Do nothing. |
|
| 359 | } |
|
| 360 | 6884 | elsif ($action eq 'next_bug' or $action eq 'same_bug') { |
| 361 | my $bug = $vars->{'bug'}; |
|
| 362 | if ($bug and $user->can_see_bug($bug)) { |
|
| 363 | if ($action eq 'same_bug') { |
|
| 364 | # $bug->update() does not update the internal structure of |
|
| 365 | # the bug sufficiently to display the bug with the new values. |
|
| 366 | # (That is, if we just passed in the old Bug object, we'd get |
|
| 367 | # a lot of old values displayed.) |
|
| 368 | $bug = new Bugzilla::Bug($bug->id); |
|
| 369 | $vars->{'bug'} = $bug; |
|
| 370 | 3493 | } |
| 371 | 3683 | $vars->{'bugs'} = [$bug]; |
| 372 | 6884 | if ($action eq 'next_bug') { |
| 373 | $vars->{'nextbug'} = $bug->id; |
|
| 374 | } |
|
| 375 | 3683 | $template->process("bug/show.html.tmpl", $vars) |
| 376 | || ThrowTemplateError($template->error()); |
|
| 377 | exit; |
|
| 378 | } |
|
| 379 | 3493 | } elsif ($action ne 'nothing') { |
| 380 | ThrowCodeError("invalid_post_bug_submit_action"); |
|
| 381 | 1 | } |
| 382 | 1341 | |
| 383 | 1390 | # End the response page. |
| 384 | 4621 | unless (Bugzilla->usage_mode == USAGE_MODE_EMAIL) { |
| 385 | $template->process("bug/navigate.html.tmpl", $vars) |
|
| 386 | || ThrowTemplateError($template->error()); |
|
| 387 | $template->process("global/footer.html.tmpl", $vars) |
|
| 388 | || ThrowTemplateError($template->error()); |
|
| 389 | } |
|
| 390 | ||
| 391 | 1; |
Loggerhead 1.18.1 is a web-based interface for Bazaar branches