RSS

(root)/bugzilla/trunk : /process_bug.cgi (revision 8182)

To get this branch, use:
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