diff options
-rwxr-xr-x | Bugzilla/Bug.pm | 84 | ||||
-rwxr-xr-x | process_bug.cgi | 177 | ||||
-rw-r--r-- | template/en/default/global/user-error.html.tmpl | 5 |
3 files changed, 122 insertions, 144 deletions
diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm index daebaf701..777fca323 100755 --- a/Bugzilla/Bug.pm +++ b/Bugzilla/Bug.pm @@ -1495,6 +1495,90 @@ sub bug_alias_to_id { } ##################################################################### +# Workflow Control routines +##################################################################### + +sub get_new_status_and_resolution { + my ($self, $action, $resolution) = @_; + my $dbh = Bugzilla->dbh; + + my $status; + if ($action eq 'none') { + # Leaving the status unchanged doesn't need more investigation. + return ($self->bug_status, $self->resolution); + } + elsif ($action eq 'reopen') { + $status = $self->everconfirmed ? 'REOPENED' : 'UNCONFIRMED'; + $resolution = ''; + } + elsif ($action =~ /^reassign(?:bycomponent)?$/) { + if (!is_open_state($self->bug_status) || $self->bug_status eq 'UNCONFIRMED') { + $status = $self->bug_status; + } + else { + $status = 'NEW'; + } + $resolution = $self->resolution; + } + elsif ($action eq 'duplicate') { + # Only alter the bug status if the bug is currently open. + $status = is_open_state($self->bug_status) ? 'RESOLVED' : $self->bug_status; + $resolution = 'DUPLICATE'; + } + elsif ($action eq 'change_resolution') { + $status = $self->bug_status; + # You cannot change the resolution of an open bug. + ThrowUserError('resolution_not_allowed') if is_open_state($status); + $resolution || ThrowUserError('missing_resolution', {status => $status}); + } + elsif ($action eq 'clearresolution') { + $status = $self->bug_status; + is_open_state($status) || ThrowUserError('missing_resolution', {status => $status}); + $resolution = ''; + } + else { + # That's where actions not requiring any specific trigger (such as future + # custom statuses) come. + # XXX - This is hardcoded here for now, but will disappear soon when + # this routine will look at the DB directly to get the workflow. + if ($action eq 'confirm') { + $status = 'NEW'; + } + elsif ($action eq 'accept') { + $status = 'ASSIGNED'; + } + elsif ($action eq 'resolve') { + $status = 'RESOLVED'; + } + elsif ($action eq 'verify') { + $status = 'VERIFIED'; + } + elsif ($action eq 'close') { + $status = 'CLOSED'; + } + else { + ThrowCodeError('unknown_action', { action => $action }); + } + + if (is_open_state($status)) { + # Open bugs have no resolution. + $resolution = ''; + } + else { + # All non-open statuses must have a resolution. + $resolution || ThrowUserError('missing_resolution', {status => $status}); + } + } + # Now it's time to validate the bug resolution. + # Bug resolutions have no workflow specific rules, so any valid + # resolution will do it. + check_field('resolution', $resolution) if ($resolution ne ''); + trick_taint($resolution); + + return ($status, $resolution); +} + +##################################################################### # Subroutines ##################################################################### diff --git a/process_bug.cgi b/process_bug.cgi index 6128bcf65..74468ada9 100755 --- a/process_bug.cgi +++ b/process_bug.cgi @@ -643,9 +643,6 @@ sub DoComma { $::comma = ","; } -# $everconfirmed is used by ChangeStatus() to determine whether we are -# confirming the bug or not. -local our $everconfirmed; sub DoConfirm { my $bug = shift; if ($bug->check_can_change_field("canconfirm", 0, 1, @@ -653,111 +650,6 @@ sub DoConfirm { { DoComma(); $::query .= "everconfirmed = 1"; - $everconfirmed = 1; - } -} - -sub ChangeStatus { - my ($str) = (@_); - my $cgi = Bugzilla->cgi; - my $dbh = Bugzilla->dbh; - - if (!$cgi->param('dontchange') - || $str ne $cgi->param('dontchange')) { - DoComma(); - if ($cgi->param('knob') eq 'reopen') { - # When reopening, we need to check whether the bug was ever - # confirmed or not - $::query .= "bug_status = CASE WHEN everconfirmed = 1 THEN " . - $dbh->quote($str) . " ELSE 'UNCONFIRMED' END"; - } elsif (is_open_state($str)) { - # Note that we cannot combine this with the above branch - here we - # need to check if bugs.bug_status is open, (since we don't want to - # reopen closed bugs when reassigning), while above the whole point - # is to reopen a closed bug. - # Currently, the UI doesn't permit a user to reassign a closed bug - # from the single bug page (only during a mass change), but they - # could still hack the submit, so don't restrict this extended - # check to the mass change page for safety/sanity/consistency - # purposes. - - # The logic for this block is: - # If the new state is open: - # If the old state was open - # If the bug was confirmed - # - move it to the new state - # Else - # - Set the state to unconfirmed - # Else - # - leave it as it was - - # This is valid only because 'reopen' is the only thing which moves - # from closed to open, and it's handled above - # This also relies on the fact that confirming and accepting have - # already called DoConfirm before this is called - - my @open_state = map($dbh->quote($_), BUG_STATE_OPEN); - my $open_state = join(", ", @open_state); - - # If we are changing everconfirmed to 1, we have to take this change - # into account and the new bug status is given by $str. - my $cond = $dbh->quote($str); - # If we are not setting everconfirmed, the new bug status depends on - # the actual value of everconfirmed, which is bug-specific. - unless ($everconfirmed) { - $cond = "(CASE WHEN everconfirmed = 1 THEN " . $cond . - " ELSE 'UNCONFIRMED' END)"; - } - $::query .= "bug_status = CASE WHEN bug_status IN($open_state) THEN " . - $cond . " ELSE bug_status END"; - } else { - $::query .= "bug_status = ?"; - push(@values, $str); - } - # If bugs are reassigned and their status is "UNCONFIRMED", they - # should keep this status instead of "NEW" as suggested here. - # This point is checked for each bug later in the code. - $cgi->param('bug_status', $str); - } -} - -sub ChangeResolution { - my ($bug, $str) = (@_); - my $dbh = Bugzilla->dbh; - my $cgi = Bugzilla->cgi; - - if (!$cgi->param('dontchange') - || $str ne $cgi->param('dontchange')) - { - # Make sure the user is allowed to change the resolution. - # If the user is changing several bugs at once using the UI, - # then he has enough privs to do so. In the case he is hacking - # the URL, we don't care if he reads --UNKNOWN-- as a resolution - # in the error message. - my $old_resolution = '-- UNKNOWN --'; - my $bug_id = $cgi->param('id'); - if ($bug_id) { - $old_resolution = - $dbh->selectrow_array('SELECT resolution FROM bugs WHERE bug_id = ?', - undef, $bug_id); - } - unless ($bug->check_can_change_field('resolution', $old_resolution, $str, - \$PrivilegesRequired)) - { - $vars->{'oldvalue'} = $old_resolution; - $vars->{'newvalue'} = $str; - $vars->{'field'} = 'resolution'; - $vars->{'privs'} = $PrivilegesRequired; - ThrowUserError("illegal_change", $vars); - } - - DoComma(); - $::query .= "resolution = ?"; - trick_taint($str); - push(@values, $str); - # We define this variable here so that customized installations - # may set rules based on the resolution in Bug::check_can_change_field(). - $cgi->param('resolution', $str); } } @@ -1042,13 +934,11 @@ SWITCH: for ($cgi->param('knob')) { }; /^confirm$/ && CheckonComment( "confirm" ) && do { DoConfirm($bug); - ChangeStatus('NEW'); last SWITCH; }; /^accept$/ && CheckonComment( "accept" ) && do { DoConfirm($bug); - ChangeStatus('ASSIGNED'); - if (Bugzilla->params->{"usetargetmilestone"} + if (Bugzilla->params->{"usetargetmilestone"} && Bugzilla->params->{"musthavemilestoneonaccept"}) { $requiremilestone = 1; @@ -1056,7 +946,6 @@ SWITCH: for ($cgi->param('knob')) { last SWITCH; }; /^clearresolution$/ && CheckonComment( "clearresolution" ) && do { - ChangeResolution($bug, ''); last SWITCH; }; /^(resolve|change_resolution)$/ && CheckonComment( "resolve" ) && do { @@ -1080,8 +969,6 @@ SWITCH: for ($cgi->param('knob')) { # RESOLVED bugs should have no time remaining; # more time can be added for the VERIFY step, if needed. _remove_remaining_time(); - - ChangeStatus('RESOLVED'); } else { # You cannot use change_resolution if there is at least @@ -1094,16 +981,12 @@ SWITCH: for ($cgi->param('knob')) { ThrowUserError('resolution_not_allowed') if $is_open; } - - ChangeResolution($bug, $cgi->param('resolution')); last SWITCH; }; /^reassign$/ && CheckonComment( "reassign" ) && do { if ($cgi->param('andconfirm')) { DoConfirm($bug); } - ChangeStatus('NEW'); - DoComma(); if (defined $cgi->param('assigned_to') && trim($cgi->param('assigned_to')) ne "") { $assignee = login_to_id(trim($cgi->param('assigned_to')), THROW_ERROR); @@ -1125,6 +1008,7 @@ SWITCH: for ($cgi->param('knob')) { } else { ThrowUserError("reassign_to_empty"); } + DoComma(); $::query .= "assigned_to = ?"; push(@values, $assignee); $assignee_checked = 1; @@ -1134,23 +1018,17 @@ SWITCH: for ($cgi->param('knob')) { if ($cgi->param('compconfirm')) { DoConfirm($bug); } - ChangeStatus('NEW'); last SWITCH; }; /^reopen$/ && CheckonComment( "reopen" ) && do { - ChangeStatus('REOPENED'); - ChangeResolution($bug, ''); last SWITCH; }; /^verify$/ && CheckonComment( "verify" ) && do { - ChangeStatus('VERIFIED'); last SWITCH; }; /^close$/ && CheckonComment( "close" ) && do { # CLOSED bugs should have no time remaining. _remove_remaining_time(); - - ChangeStatus('CLOSED'); last SWITCH; }; /^duplicate$/ && CheckonComment( "duplicate" ) && do { @@ -1196,9 +1074,6 @@ SWITCH: for ($cgi->param('knob')) { # DUPLICATE bugs should have no time remaining. _remove_remaining_time(); - - ChangeStatus('RESOLVED'); - ChangeResolution($bug, 'DUPLICATE'); last SWITCH; }; @@ -1391,8 +1266,30 @@ if ($prod_changed && Bugzilla->params->{"strict_isolation"}) { foreach my $id (@idlist) { my $query = $basequery; my @bug_values = @values; + # XXX We really have to get rid of $::comma. + my $comma = $::comma; my $old_bug_obj = new Bugzilla::Bug($id); + my $status; + my $resolution = $old_bug_obj->resolution; + # These are the only actions where we care about the resolution field. + if ($cgi->param('knob') =~ /^(?:resolve|change_resolution)$/) { + $resolution = $cgi->param('resolution'); + } + ($status, $resolution) = + $old_bug_obj->get_new_status_and_resolution(scalar $cgi->param('knob'), $resolution); + + if ($status ne $old_bug_obj->bug_status) { + $query .= "$comma bug_status = ?"; + push(@bug_values, $status); + $comma = ','; + } + if ($resolution ne $old_bug_obj->resolution) { + $query .= "$comma resolution = ?"; + push(@bug_values, $resolution); + $comma = ','; + } + if ($cgi->param('knob') eq 'reassignbycomponent') { # We have to check whether the bug is moved to another product # and/or component before reassigning. If $component is defined, @@ -1402,24 +1299,23 @@ foreach my $id (@idlist) { FROM components WHERE components.id = ?', undef, $new_comp_id); - $query .= ", assigned_to = ?"; + $query .= "$comma assigned_to = ?"; push(@bug_values, $assignee); + $comma = ','; if (Bugzilla->params->{"useqacontact"}) { $qacontact = $dbh->selectrow_array('SELECT initialqacontact FROM components WHERE components.id = ?', undef, $new_comp_id); if ($qacontact) { - $query .= ", qa_contact = ?"; + $query .= "$comma qa_contact = ?"; push(@bug_values, $qacontact); } else { - $query .= ", qa_contact = NULL"; + $query .= "$comma qa_contact = NULL"; } } - - # And add in the Default CC for the Component. my $comp_obj = $component || new Bugzilla::Component($new_comp_id); my @new_init_cc = @{$comp_obj->initial_cc}; @@ -1466,20 +1362,18 @@ foreach my $id (@idlist) { } $oldhash{$col} = $oldvalues[$i]; $formhash{$col} = $cgi->param($col) if defined $cgi->param($col); + # The status and resolution are defined by the workflow. + $formhash{$col} = $status if $col eq 'bug_status'; + $formhash{$col} = $resolution if $col eq 'resolution'; $i++; } # If the user is reassigning bugs, we need to: # - convert $newhash{'assigned_to'} and $newhash{'qa_contact'} # email addresses into their corresponding IDs; - # - update $newhash{'bug_status'} to its real state if the bug - # is in the unconfirmed state. $formhash{'qa_contact'} = $qacontact if Bugzilla->params->{'useqacontact'}; if ($cgi->param('knob') eq 'reassignbycomponent' || $cgi->param('knob') eq 'reassign') { $formhash{'assigned_to'} = $assignee; - if ($oldhash{'bug_status'} eq 'UNCONFIRMED') { - $formhash{'bug_status'} = $oldhash{'bug_status'}; - } } # This hash is required by Bug::check_can_change_field(). my $cgi_hash = { @@ -1487,9 +1381,6 @@ foreach my $id (@idlist) { 'knob' => scalar $cgi->param('knob') }; foreach my $col (@editable_bug_fields) { - # The 'resolution' field is checked by ChangeResolution(), - # i.e. only if we effectively use it. - next if ($col eq 'resolution'); if (exists $formhash{$col} && !$old_bug_obj->check_can_change_field($col, $oldhash{$col}, $formhash{$col}, \$PrivilegesRequired, $cgi_hash)) @@ -1665,14 +1556,12 @@ foreach my $id (@idlist) { } $query .= " WHERE bug_id = ?"; push(@bug_values, $id); - - if ($::comma ne "") { + + if ($comma ne '') { $dbh->do($query, undef, @bug_values); } # Check for duplicates if the bug is [re]open or its resolution is changed. - my $resolution = $dbh->selectrow_array( - q{SELECT resolution FROM bugs WHERE bug_id = ?}, undef, $id); if ($resolution ne 'DUPLICATE') { $dbh->do(q{DELETE FROM duplicates WHERE dupe = ?}, undef, $id); } diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl index 171eb9c20..4fa138206 100644 --- a/template/en/default/global/user-error.html.tmpl +++ b/template/en/default/global/user-error.html.tmpl @@ -977,6 +977,11 @@ does not exist. [% END %] + [% ELSIF error == "missing_resolution" %] + [% title = "Resolution Required" %] + A valid resolution is required to mark [% terms.bugs %] as + [%+ status FILTER upper FILTER html %]. + [% ELSIF error == "move_bugs_disabled" %] [% title = BLOCK %][% terms.Bug %] Moving Disabled[% END %] Sorry, [% terms.bug %] moving has been disabled. If you need |