Commit 2bd57ce8 authored by lpsolit%gmail.com's avatar lpsolit%gmail.com

Bug 344965: Fix process_bug.cgi and bug/* templates to work with custom bug…

Bug 344965: Fix process_bug.cgi and bug/* templates to work with custom bug status workflow - Patch by Fré©ric Buclin <LpSolit@gmail.com> r=mkanat a=LpSolit
parent 9e81bb03
...@@ -42,6 +42,7 @@ use Bugzilla::Error; ...@@ -42,6 +42,7 @@ use Bugzilla::Error;
use Bugzilla::Product; use Bugzilla::Product;
use Bugzilla::Component; use Bugzilla::Component;
use Bugzilla::Group; use Bugzilla::Group;
use Bugzilla::Status;
use List::Util qw(min); use List::Util qw(min);
use Storable qw(dclone); use Storable qw(dclone);
...@@ -52,8 +53,9 @@ use base qw(Bugzilla::Object Exporter); ...@@ -52,8 +53,9 @@ use base qw(Bugzilla::Object Exporter);
bug_alias_to_id ValidateBugAlias ValidateBugID bug_alias_to_id ValidateBugAlias ValidateBugID
RemoveVotes CheckIfVotedConfirmed RemoveVotes CheckIfVotedConfirmed
LogActivityEntry LogActivityEntry
is_open_state BUG_STATE_OPEN is_open_state
editable_bug_fields editable_bug_fields
SPECIAL_STATUS_WORKFLOW_ACTIONS
); );
##################################################################### #####################################################################
...@@ -176,6 +178,19 @@ use constant VALID_ENTRY_STATUS => qw( ...@@ -176,6 +178,19 @@ use constant VALID_ENTRY_STATUS => qw(
ASSIGNED ASSIGNED
); );
use constant SPECIAL_STATUS_WORKFLOW_ACTIONS => qw(
none
duplicate
change_resolution
clearresolution
);
sub BUG_STATE_OPEN {
# XXX - We should cache this list.
my $dbh = Bugzilla->dbh;
return @{$dbh->selectcol_arrayref('SELECT value FROM bug_status WHERE is_open = 1')};
}
##################################################################### #####################################################################
sub new { sub new {
...@@ -213,12 +228,6 @@ sub new { ...@@ -213,12 +228,6 @@ sub new {
return $error_self; return $error_self;
} }
# XXX At some point these should be moved into accessors.
# They only are here because this is how Bugzilla::Bug
# originally did things, before it was a Bugzilla::Object.
$self->{'isunconfirmed'} = ($self->{bug_status} eq 'UNCONFIRMED');
$self->{'isopened'} = is_open_state($self->{bug_status});
return $self; return $self;
} }
...@@ -1025,8 +1034,7 @@ sub set_status { ...@@ -1025,8 +1034,7 @@ sub set_status {
my ($self, $status) = @_; my ($self, $status) = @_;
$self->set('bug_status', $status); $self->set('bug_status', $status);
# Check for the everconfirmed transition # Check for the everconfirmed transition
$self->_set_everconfirmed(1) if ($status eq 'NEW' $self->_set_everconfirmed(1) if (is_open_state($status) && $status ne 'UNCONFIRMED');
|| $status eq 'ASSIGNED');
} }
######################## ########################
...@@ -1247,6 +1255,16 @@ sub flag_types { ...@@ -1247,6 +1255,16 @@ sub flag_types {
return $self->{'flag_types'}; return $self->{'flag_types'};
} }
sub isopened {
my $self = shift;
return is_open_state($self->{bug_status}) ? 1 : 0;
}
sub isunconfirmed {
my $self = shift;
return ($self->bug_status eq 'UNCONFIRMED') ? 1 : 0;
}
sub keywords { sub keywords {
my ($self) = @_; my ($self) = @_;
return $self->{'keywords'} if exists $self->{'keywords'}; return $self->{'keywords'} if exists $self->{'keywords'};
...@@ -1317,6 +1335,13 @@ sub reporter { ...@@ -1317,6 +1335,13 @@ sub reporter {
return $self->{'reporter'}; return $self->{'reporter'};
} }
sub status {
my $self = shift;
return undef if $self->{'error'};
$self->{'status'} ||= new Bugzilla::Status({name => $self->{'bug_status'}});
return $self->{'status'};
}
sub show_attachment_flags { sub show_attachment_flags {
my ($self) = @_; my ($self) = @_;
...@@ -1530,18 +1555,159 @@ sub bug_alias_to_id { ...@@ -1530,18 +1555,159 @@ sub bug_alias_to_id {
# Workflow Control routines # Workflow Control routines
##################################################################### #####################################################################
# Make sure that the new status is valid for ALL bugs.
sub check_status_transition {
my ($self, $new_status, $bug_ids) = @_;
my $dbh = Bugzilla->dbh;
check_field('bug_status', $new_status);
trick_taint($new_status);
my $illegal_statuses =
$dbh->selectcol_arrayref('SELECT DISTINCT bug_status.value
FROM bug_status
INNER JOIN bugs
ON bugs.bug_status = bug_status.value
WHERE bug_id IN (' . join (',', @$bug_ids). ')
AND bug_status.id NOT IN (SELECT old_status
FROM status_workflow
INNER JOIN bug_status b_s
ON b_s.id = status_workflow.new_status
WHERE b_s.value = ?)',
undef, $new_status);
if (scalar(@$illegal_statuses)) {
ThrowUserError('illegal_bug_status_transition', {old => $illegal_statuses,
new => $new_status})
}
}
# Make sure all checks triggered by the workflow are successful.
# Some are hardcoded and come from older versions of Bugzilla.
sub check_status_change_triggers {
my ($self, $action, $bug_ids, $vars) = @_;
my $dbh = Bugzilla->dbh;
$vars ||= {};
# First, make sure no comment is required if there is none.
# If a comment is given, then this check is useless.
if (!$vars->{comment_exists}) {
if (grep { $action eq $_ } SPECIAL_STATUS_WORKFLOW_ACTIONS) {
# 'commentonnone' doesn't exist, so this is safe.
ThrowUserError('comment_required') if Bugzilla->params->{"commenton$action"};
}
else {
my $required_for_transitions =
$dbh->selectcol_arrayref('SELECT DISTINCT bug_status.value
FROM bug_status
INNER JOIN bugs
ON bugs.bug_status = bug_status.value
INNER JOIN status_workflow
ON bug_status.id = old_status
INNER JOIN bug_status b_s
ON b_s.id = new_status
WHERE bug_id IN (' . join (',', @$bug_ids). ')
AND b_s.value = ?
AND require_comment = 1',
undef, $action);
if (scalar(@$required_for_transitions)) {
ThrowUserError('comment_required', {old => $required_for_transitions,
new => $action});
}
}
}
# Now run hardcoded checks.
# There is no checks for these actions.
return if ($action eq 'none' || $action eq 'clearresolution');
if ($action eq 'duplicate') {
# You cannot mark bugs as duplicates when changing
# several bugs at once.
$vars->{bug_id} || ThrowUserError('dupe_not_allowed');
# Make sure we can change the original bug (issue A on bug 96085)
$vars->{dup_id} || ThrowCodeError('undefined_field', { field => 'dup_id' });
ValidateBugID($vars->{dup_id}, 'dup_id');
# Make sure a loop isn't created when marking this bug
# as duplicate.
my %dupes;
my $dupe_of = $vars->{dup_id};
my $sth = $dbh->prepare('SELECT dupe_of FROM duplicates
WHERE dupe = ?');
while ($dupe_of) {
if ($dupe_of == $vars->{bug_id}) {
ThrowUserError('dupe_loop_detected', { bug_id => $vars->{bug_id},
dupe_of => $vars->{dup_id} });
}
# If $dupes{$dupe_of} is already set to 1, then a loop
# already exists which does not involve this bug.
# As the user is not responsible for this loop, do not
# prevent him from marking this bug as a duplicate.
last if exists $dupes{"$dupe_of"};
$dupes{"$dupe_of"} = 1;
$sth->execute($dupe_of);
$dupe_of = $sth->fetchrow_array;
}
# Also, let's see if the reporter has authorization to see
# the bug to which we are duping. If not we need to prompt.
$vars->{DuplicateUserConfirm} = 1;
# DUPLICATE bugs should have no time remaining.
$vars->{remove_remaining_time} = 1;
}
elsif ($action eq 'change_resolution' || !is_open_state($action)) {
# don't resolve as fixed while still unresolved blocking bugs
if (Bugzilla->params->{"noresolveonopenblockers"}
&& $vars->{resolution} eq 'FIXED')
{
my @dependencies = Bugzilla::Bug::CountOpenDependencies(@$bug_ids);
if (scalar @dependencies > 0) {
ThrowUserError("still_unresolved_bugs",
{ dependencies => \@dependencies,
dependency_count => scalar @dependencies });
}
}
# You cannot use change_resolution if there is at least one open bug
# nor can you close open bugs if no resolution is given.
my $open_states = join(',', map {$dbh->quote($_)} BUG_STATE_OPEN);
my $idlist = join(',', @$bug_ids);
my $is_open =
$dbh->selectrow_array("SELECT 1 FROM bugs WHERE bug_id IN ($idlist)
AND bug_status IN ($open_states)");
if ($is_open) {
ThrowUserError('resolution_not_allowed') if ($action eq 'change_resolution');
ThrowUserError('missing_resolution', {status => $action}) if !$vars->{resolution};
}
# Now is good time to validate the resolution, if any.
check_field('resolution', $vars->{resolution},
Bugzilla::Bug->settable_resolutions) if $vars->{resolution};
$vars->{remove_remaining_time} = 1 if ($action ne 'change_resolution');
}
elsif ($action eq 'ASSIGNED'
&& Bugzilla->params->{"usetargetmilestone"}
&& Bugzilla->params->{"musthavemilestoneonaccept"})
{
$vars->{requiremilestone} = 1;
}
}
sub get_new_status_and_resolution { sub get_new_status_and_resolution {
my ($self, $action, $resolution) = @_; my ($self, $action, $resolution) = @_;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $status; my $status;
my $everconfirmed = $self->everconfirmed;
if ($action eq 'none') { if ($action eq 'none') {
# Leaving the status unchanged doesn't need more investigation. # Leaving the status unchanged doesn't need more investigation.
return ($self->bug_status, $self->resolution); return ($self->bug_status, $self->resolution, $self->everconfirmed);
}
elsif ($action eq 'reopen') {
$status = $self->everconfirmed ? 'REOPENED' : 'UNCONFIRMED';
$resolution = '';
} }
elsif ($action eq 'duplicate') { elsif ($action eq 'duplicate') {
# Only alter the bug status if the bug is currently open. # Only alter the bug status if the bug is currently open.
...@@ -1560,37 +1726,21 @@ sub get_new_status_and_resolution { ...@@ -1560,37 +1726,21 @@ sub get_new_status_and_resolution {
$resolution = ''; $resolution = '';
} }
else { else {
# That's where actions not requiring any specific trigger (such as future $status = $action;
# 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)) { if (is_open_state($status)) {
# Open bugs have no resolution. # Open bugs have no resolution.
$resolution = ''; $resolution = '';
$everconfirmed = ($status eq 'UNCONFIRMED') ? 0 : 1;
} }
else { elsif (is_open_state($self->bug_status)) {
# All non-open statuses must have a resolution. # A resolution is required to close bugs.
$resolution || ThrowUserError('missing_resolution', {status => $status}); $resolution || ThrowUserError('missing_resolution', {status => $status});
} }
else {
# Already closed bugs can only change their resolution
# using the change_resolution action.
$resolution = $self->resolution
}
} }
# Now it's time to validate the bug resolution. # Now it's time to validate the bug resolution.
# Bug resolutions have no workflow specific rules, so any valid # Bug resolutions have no workflow specific rules, so any valid
...@@ -1598,7 +1748,7 @@ sub get_new_status_and_resolution { ...@@ -1598,7 +1748,7 @@ sub get_new_status_and_resolution {
check_field('resolution', $resolution) if ($resolution ne ''); check_field('resolution', $resolution) if ($resolution ne '');
trick_taint($resolution); trick_taint($resolution);
return ($status, $resolution); return ($status, $resolution, $everconfirmed);
} }
##################################################################### #####################################################################
...@@ -1968,7 +2118,7 @@ sub CountOpenDependencies { ...@@ -1968,7 +2118,7 @@ sub CountOpenDependencies {
"FROM bugs, dependencies " . "FROM bugs, dependencies " .
"WHERE blocked IN (" . (join "," , @bug_list) . ") " . "WHERE blocked IN (" . (join "," , @bug_list) . ") " .
"AND bug_id = dependson " . "AND bug_id = dependson " .
"AND bug_status IN ('" . (join "','", BUG_STATE_OPEN) . "') " . "AND bug_status IN (" . join(', ', map {$dbh->quote($_)} BUG_STATE_OPEN) . ") " .
$dbh->sql_group_by('blocked')); $dbh->sql_group_by('blocked'));
$sth->execute(); $sth->execute();
......
...@@ -120,8 +120,6 @@ use File::Basename; ...@@ -120,8 +120,6 @@ use File::Basename;
FIELD_TYPE_FREETEXT FIELD_TYPE_FREETEXT
FIELD_TYPE_SINGLE_SELECT FIELD_TYPE_SINGLE_SELECT
BUG_STATE_OPEN
USAGE_MODE_BROWSER USAGE_MODE_BROWSER
USAGE_MODE_CMDLINE USAGE_MODE_CMDLINE
USAGE_MODE_WEBSERVICE USAGE_MODE_WEBSERVICE
...@@ -351,10 +349,6 @@ use constant SAFE_PROTOCOLS => ('afs', 'cid', 'ftp', 'gopher', 'http', 'https', ...@@ -351,10 +349,6 @@ use constant SAFE_PROTOCOLS => ('afs', 'cid', 'ftp', 'gopher', 'http', 'https',
'irc', 'mid', 'news', 'nntp', 'prospero', 'telnet', 'irc', 'mid', 'news', 'nntp', 'prospero', 'telnet',
'view-source', 'wais'); 'view-source', 'wais');
# States that are considered to be "open" for bugs.
use constant BUG_STATE_OPEN => ('NEW', 'REOPENED', 'ASSIGNED',
'UNCONFIRMED');
# Usage modes. Default USAGE_MODE_BROWSER. Use with Bugzilla->usage_mode. # Usage modes. Default USAGE_MODE_BROWSER. Use with Bugzilla->usage_mode.
use constant USAGE_MODE_BROWSER => 0; use constant USAGE_MODE_BROWSER => 0;
use constant USAGE_MODE_CMDLINE => 1; use constant USAGE_MODE_CMDLINE => 1;
......
...@@ -22,7 +22,7 @@ package Bugzilla::Install::DB; ...@@ -22,7 +22,7 @@ package Bugzilla::Install::DB;
use strict; use strict;
use Bugzilla::Bug qw(is_open_state); use Bugzilla::Bug qw(BUG_STATE_OPEN is_open_state);
use Bugzilla::Constants; use Bugzilla::Constants;
use Bugzilla::Hook; use Bugzilla::Hook;
use Bugzilla::Install::Util qw(indicate_progress); use Bugzilla::Install::Util qw(indicate_progress);
......
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Frédéric Buclin.
# Portions created by Frédéric Buclin are Copyright (C) 2007
# Frédéric Buclin. All Rights Reserved.
#
# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
use strict;
package Bugzilla::Status;
use base qw(Bugzilla::Object);
################################
##### Initialization #####
################################
use constant DB_TABLE => 'bug_status';
use constant DB_COLUMNS => qw(
id
value
sortkey
isactive
is_open
);
use constant NAME_FIELD => 'value';
use constant LIST_ORDER => 'sortkey, value';
###############################
##### Accessors ####
###############################
sub name { return $_[0]->{'value'}; }
sub sortkey { return $_[0]->{'sortkey'}; }
sub is_active { return $_[0]->{'isactive'}; }
sub is_open { return $_[0]->{'is_open'}; }
###############################
##### Methods ####
###############################
sub can_change_to {
my $self = shift;
my $dbh = Bugzilla->dbh;
if (!defined $self->{'can_change_to'}) {
my $new_status_ids = $dbh->selectcol_arrayref('SELECT new_status
FROM status_workflow
INNER JOIN bug_status
ON id = new_status
WHERE isactive = 1
AND old_status = ?',
undef, $self->id);
$self->{'can_change_to'} = Bugzilla::Status->new_from_list($new_status_ids);
}
return $self->{'can_change_to'};
}
1;
__END__
=head1 NAME
Bugzilla::Status - Bug status class.
=head1 SYNOPSIS
use Bugzilla::Status;
my $bug_status = new Bugzilla::Status({name => 'ASSIGNED'});
my $bug_status = new Bugzilla::Status(4);
=head1 DESCRIPTION
Status.pm represents a bug status object. It is an implementation
of L<Bugzilla::Object>, and thus provides all methods that
L<Bugzilla::Object> provides.
The methods that are specific to C<Bugzilla::Status> are listed
below.
=head1 METHODS
=over
=item C<can_change_to>
Description: Returns the list of active statuses a bug can be changed to
given the current bug status.
Params: none.
Returns: A list of Bugzilla::Status objects.
=back
=cut
...@@ -1139,8 +1139,26 @@ if ($dotweak) { ...@@ -1139,8 +1139,26 @@ if ($dotweak) {
$vars->{'unconfirmedstate'} = 'UNCONFIRMED'; $vars->{'unconfirmedstate'} = 'UNCONFIRMED';
$vars->{'bugstatuses'} = [ keys %$bugstatuses ]; # Convert bug statuses to their ID.
my @bug_statuses = map {$dbh->quote($_)} keys %$bugstatuses;
my $bug_status_ids =
$dbh->selectcol_arrayref('SELECT id FROM bug_status
WHERE value IN (' . join(', ', @bug_statuses) .')');
# This query collects new statuses which are common to all current bug statuses.
# It also accepts transitions where the bug status doesn't change.
$bug_status_ids =
$dbh->selectcol_arrayref('SELECT DISTINCT new_status
FROM status_workflow sw1
WHERE NOT EXISTS (SELECT * FROM status_workflow sw2
WHERE sw2.old_status != sw1.new_status
AND sw2.old_status IN (' . join(', ', @$bug_status_ids) . ')
AND NOT EXISTS (SELECT * FROM status_workflow sw3
WHERE sw3.new_status = sw1.new_status
AND sw3.old_status = sw2.old_status))');
$vars->{'current_bug_statuses'} = [keys %$bugstatuses];
$vars->{'new_bug_statuses'} = Bugzilla::Status->new_from_list($bug_status_ids);
# The groups to which the user belongs. # The groups to which the user belongs.
$vars->{'groups'} = GetGroups(); $vars->{'groups'} = GetGroups();
......
...@@ -27,6 +27,7 @@ use Bugzilla; ...@@ -27,6 +27,7 @@ use Bugzilla;
use Bugzilla::Constants; use Bugzilla::Constants;
use Bugzilla::Error; use Bugzilla::Error;
use Bugzilla::Token; use Bugzilla::Token;
use Bugzilla::Status;
my $cgi = Bugzilla->cgi; my $cgi = Bugzilla->cgi;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
...@@ -42,12 +43,6 @@ $user->in_group('admin') ...@@ -42,12 +43,6 @@ $user->in_group('admin')
my $action = $cgi->param('action') || 'edit'; my $action = $cgi->param('action') || 'edit';
my $token = $cgi->param('token'); my $token = $cgi->param('token');
sub get_statuses {
my $statuses = $dbh->selectall_arrayref('SELECT id, value, is_open FROM bug_status
ORDER BY sortkey, value', { Slice => {} });
return $statuses;
}
sub get_workflow { sub get_workflow {
my $workflow = $dbh->selectall_arrayref('SELECT old_status, new_status, require_comment my $workflow = $dbh->selectall_arrayref('SELECT old_status, new_status, require_comment
FROM status_workflow'); FROM status_workflow');
...@@ -64,7 +59,7 @@ sub load_template { ...@@ -64,7 +59,7 @@ sub load_template {
my $template = Bugzilla->template; my $template = Bugzilla->template;
my $vars = {}; my $vars = {};
$vars->{'statuses'} = get_statuses(); $vars->{'statuses'} = [Bugzilla::Status->get_all];
$vars->{'workflow'} = get_workflow(); $vars->{'workflow'} = get_workflow();
$vars->{'token'} = issue_session_token("workflow_$filename"); $vars->{'token'} = issue_session_token("workflow_$filename");
$vars->{'message'} = $message; $vars->{'message'} = $message;
...@@ -79,9 +74,8 @@ if ($action eq 'edit') { ...@@ -79,9 +74,8 @@ if ($action eq 'edit') {
} }
elsif ($action eq 'update') { elsif ($action eq 'update') {
check_token_data($token, 'workflow_edit'); check_token_data($token, 'workflow_edit');
my $statuses = get_statuses; my $statuses = [Bugzilla::Status->get_all];
my $workflow = get_workflow(); my $workflow = get_workflow();
my $initial_state = {id => 0};
my $sth_insert = $dbh->prepare('INSERT INTO status_workflow (old_status, new_status) my $sth_insert = $dbh->prepare('INSERT INTO status_workflow (old_status, new_status)
VALUES (?, ?)'); VALUES (?, ?)');
...@@ -90,22 +84,28 @@ elsif ($action eq 'update') { ...@@ -90,22 +84,28 @@ elsif ($action eq 'update') {
my $sth_delnul = $dbh->prepare('DELETE FROM status_workflow my $sth_delnul = $dbh->prepare('DELETE FROM status_workflow
WHERE old_status IS NULL AND new_status = ?'); WHERE old_status IS NULL AND new_status = ?');
foreach my $old ($initial_state, @$statuses) { # Part 1: Initial bug statuses.
# Hashes cannot have undef as a key, so we use 0. But the DB
# must store undef, for referential integrity.
my $old_id_for_db = $old->{'id'} || undef;
foreach my $new (@$statuses) { foreach my $new (@$statuses) {
next if $old->{'id'} == $new->{'id'}; if ($cgi->param('w_0_' . $new->id)) {
$sth_insert->execute(undef, $new->id)
if ($cgi->param('w_' . $old->{'id'} . '_' . $new->{'id'})) { unless defined $workflow->{0}->{$new->id};
$sth_insert->execute($old_id_for_db, $new->{'id'})
unless defined $workflow->{$old->{'id'}}->{$new->{'id'}};
} }
elsif ($old_id_for_db) { else {
$sth_delete->execute($old_id_for_db, $new->{'id'}); $sth_delnul->execute($new->id);
}
}
# Part 2: Bug status changes.
foreach my $old (@$statuses) {
foreach my $new (@$statuses) {
next if $old->id == $new->id;
if ($cgi->param('w_' . $old->id . '_' . $new->id)) {
$sth_insert->execute($old->id, $new->id)
unless defined $workflow->{$old->id}->{$new->id};
} }
else { else {
$sth_delnul->execute($new->{'id'}); $sth_delete->execute($old->id, $new->id);
} }
} }
} }
......
...@@ -120,6 +120,11 @@ sub send_results { ...@@ -120,6 +120,11 @@ sub send_results {
$vars->{'header_done'} = 1; $vars->{'header_done'} = 1;
} }
sub comment_exists {
my $cgi = Bugzilla->cgi;
return ($cgi->param('comment') && $cgi->param('comment') =~ /\S+/) ? 1 : 0;
}
###################################################################### ######################################################################
# Begin Data/Security Validation # Begin Data/Security Validation
###################################################################### ######################################################################
...@@ -244,32 +249,6 @@ if ($cgi->cookie("BUGLIST") && defined $cgi->param('id')) { ...@@ -244,32 +249,6 @@ if ($cgi->cookie("BUGLIST") && defined $cgi->param('id')) {
$vars->{'bug_list'} = \@bug_list; $vars->{'bug_list'} = \@bug_list;
} }
# This function checks if there is a comment required for a specific
# function and tests, if the comment was given.
# If comments are required for functions is defined by params.
#
sub CheckonComment {
my ($function) = (@_);
my $cgi = Bugzilla->cgi;
# Param is 1 if comment should be added !
my $ret = Bugzilla->params->{ "commenton" . $function };
# Allow without comment in case of undefined Params.
$ret = 0 unless ( defined( $ret ));
if( $ret ) {
if (!defined $cgi->param('comment')
|| $cgi->param('comment') =~ /^\s*$/) {
# No comment - sorry, action not allowed !
ThrowUserError("comment_required");
} else {
$ret = 0;
}
}
return( ! $ret ); # Return val has to be inverted
}
# Figure out whether or not the user is trying to change the product # Figure out whether or not the user is trying to change the product
# (either the "product" variable is not set to "don't change" or the # (either the "product" variable is not set to "don't change" or the
# user is changing a single bug and has changed the bug's product), # user is changing a single bug and has changed the bug's product),
...@@ -287,11 +266,13 @@ if (defined $cgi->param('id')) { ...@@ -287,11 +266,13 @@ if (defined $cgi->param('id')) {
defined($cgi->param('product')) defined($cgi->param('product'))
|| ThrowCodeError('undefined_field', { field => 'product' }); || ThrowCodeError('undefined_field', { field => 'product' });
if (((defined $cgi->param('id') && $cgi->param('product') ne $oldproduct) if ((defined $cgi->param('id') && $cgi->param('product') ne $oldproduct)
|| (!$cgi->param('id') || (!$cgi->param('id')
&& $cgi->param('product') ne $cgi->param('dontchange'))) && $cgi->param('product') ne $cgi->param('dontchange')))
&& CheckonComment( "reassignbycomponent" ))
{ {
if (Bugzilla->params->{'commentonreassignbycomponent'} && !comment_exists()) {
ThrowUserError('comment_required');
}
# Check to make sure they actually have the right to change the product # Check to make sure they actually have the right to change the product
if (!$bug->check_can_change_field('product', $oldproduct, $cgi->param('product'), if (!$bug->check_can_change_field('product', $oldproduct, $cgi->param('product'),
\$PrivilegesRequired)) \$PrivilegesRequired))
...@@ -439,6 +420,7 @@ defined($cgi->param('component')) ...@@ -439,6 +420,7 @@ defined($cgi->param('component'))
# Confirm that the reporter of the current bug can access the bug we are duping to. # Confirm that the reporter of the current bug can access the bug we are duping to.
sub DuplicateUserConfirm { sub DuplicateUserConfirm {
my ($dupe, $original) = @_;
my $cgi = Bugzilla->cgi; my $cgi = Bugzilla->cgi;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $template = Bugzilla->template; my $template = Bugzilla->template;
...@@ -448,11 +430,6 @@ sub DuplicateUserConfirm { ...@@ -448,11 +430,6 @@ sub DuplicateUserConfirm {
return; return;
} }
# Remember that we validated both these ids earlier, so we know
# they are both valid bug ids
my $dupe = $cgi->param('id');
my $original = $cgi->param('dup_id');
my $reporter = $dbh->selectrow_array( my $reporter = $dbh->selectrow_array(
q{SELECT reporter FROM bugs WHERE bug_id = ?}, undef, $dupe); q{SELECT reporter FROM bugs WHERE bug_id = ?}, undef, $dupe);
my $rep_user = Bugzilla::User->new($reporter); my $rep_user = Bugzilla::User->new($reporter);
...@@ -618,16 +595,6 @@ sub DoComma { ...@@ -618,16 +595,6 @@ sub DoComma {
$::comma = ","; $::comma = ",";
} }
sub DoConfirm {
my $bug = shift;
if ($bug->check_can_change_field("canconfirm", 0, 1,
\$PrivilegesRequired))
{
DoComma();
$::query .= "everconfirmed = 1";
}
}
# Changing this so that it will process groups from checkboxes instead of # Changing this so that it will process groups from checkboxes instead of
# select lists. This means that instead of looking for the bit-X values in # select lists. This means that instead of looking for the bit-X values in
# the form, we need to loop through all the bug groups this user has access # the form, we need to loop through all the bug groups this user has access
...@@ -941,126 +908,53 @@ if (defined $cgi->param('qa_contact') && !$cgi->param('set_default_qa_contact')) ...@@ -941,126 +908,53 @@ if (defined $cgi->param('qa_contact') && !$cgi->param('set_default_qa_contact'))
} }
} }
if ($cgi->param('set_default_assignee') || $cgi->param('set_default_qa_contact')) { if (($cgi->param('set_default_assignee') || $cgi->param('set_default_qa_contact'))
CheckonComment('reassignbycomponent'); && Bugzilla->params->{'commentonreassignbycomponent'} && !comment_exists())
{
ThrowUserError('comment_required');
} }
my $duplicate; # It will store the ID of the bug we are pointing to, if any. my $duplicate; # It will store the ID of the bug we are pointing to, if any.
SWITCH: for ($cgi->param('knob')) { # Make sure the bug status transition is legal for all bugs.
/^none$/ && do { my $knob = scalar $cgi->param('knob');
last SWITCH; # Special actions (duplicate, change_resolution and clearresolution) are outside
}; # the workflow.
/^confirm$/ && CheckonComment( "confirm" ) && do { if (!grep { $knob eq $_ } SPECIAL_STATUS_WORKFLOW_ACTIONS) {
DoConfirm($bug); Bugzilla::Bug->check_status_transition($knob, \@idlist);
last SWITCH; my $bug_status = new Bugzilla::Status({name => $knob});
}; # Fill the resolution field with the correct value (e.g. in case the
/^accept$/ && CheckonComment( "accept" ) && do { # workflow allows several open -> closed transitions).
DoConfirm($bug); if ($bug_status->is_open) {
if (Bugzilla->params->{"usetargetmilestone"} $cgi->delete('resolution');
&& Bugzilla->params->{"musthavemilestoneonaccept"})
{
$requiremilestone = 1;
}
last SWITCH;
};
/^clearresolution$/ && CheckonComment( "clearresolution" ) && do {
last SWITCH;
};
/^(resolve|change_resolution)$/ && CheckonComment( "resolve" ) && do {
# Check here, because it's the only place we require the resolution
check_field('resolution', scalar $cgi->param('resolution'),
Bugzilla::Bug->settable_resolutions);
# don't resolve as fixed while still unresolved blocking bugs
if (Bugzilla->params->{"noresolveonopenblockers"}
&& $cgi->param('resolution') eq 'FIXED')
{
my @dependencies = Bugzilla::Bug::CountOpenDependencies(@idlist);
if (scalar @dependencies > 0) {
ThrowUserError("still_unresolved_bugs",
{ dependencies => \@dependencies,
dependency_count => scalar @dependencies });
}
}
if ($cgi->param('knob') eq 'resolve') {
# RESOLVED bugs should have no time remaining;
# more time can be added for the VERIFY step, if needed.
_remove_remaining_time();
} }
else { else {
# You cannot use change_resolution if there is at least $cgi->param('resolution', $cgi->param('resolution_knob_' . $bug_status->id));
# one open bug. }
my $open_states = join(',', map {$dbh->quote($_)} BUG_STATE_OPEN);
my $idlist = join(',', @idlist);
my $is_open =
$dbh->selectrow_array("SELECT 1 FROM bugs WHERE bug_id IN ($idlist)
AND bug_status IN ($open_states)");
ThrowUserError('resolution_not_allowed') if $is_open;
}
last SWITCH;
};
/^reopen$/ && CheckonComment( "reopen" ) && do {
last SWITCH;
};
/^verify$/ && CheckonComment( "verify" ) && do {
last SWITCH;
};
/^close$/ && CheckonComment( "close" ) && do {
# CLOSED bugs should have no time remaining.
_remove_remaining_time();
last SWITCH;
};
/^duplicate$/ && CheckonComment( "duplicate" ) && do {
# You cannot mark bugs as duplicates when changing
# several bugs at once.
unless (defined $cgi->param('id')) {
ThrowUserError('dupe_not_allowed');
}
# Make sure we can change the original bug (issue A on bug 96085)
defined($cgi->param('dup_id'))
|| ThrowCodeError('undefined_field', { field => 'dup_id' });
$duplicate = $cgi->param('dup_id');
ValidateBugID($duplicate, 'dup_id');
$cgi->param('dup_id', $duplicate);
# Make sure a loop isn't created when marking this bug
# as duplicate.
my %dupes;
my $dupe_of = $duplicate;
my $sth = $dbh->prepare('SELECT dupe_of FROM duplicates
WHERE dupe = ?');
while ($dupe_of) {
if ($dupe_of == $cgi->param('id')) {
ThrowUserError('dupe_loop_detected', { bug_id => $cgi->param('id'),
dupe_of => $duplicate });
}
# If $dupes{$dupe_of} is already set to 1, then a loop
# already exists which does not involve this bug.
# As the user is not responsible for this loop, do not
# prevent him from marking this bug as a duplicate.
last if exists $dupes{"$dupe_of"};
$dupes{"$dupe_of"} = 1;
$sth->execute($dupe_of);
$dupe_of = $sth->fetchrow_array;
}
# Also, let's see if the reporter has authorization to see
# the bug to which we are duping. If not we need to prompt.
DuplicateUserConfirm();
# DUPLICATE bugs should have no time remaining.
_remove_remaining_time();
last SWITCH;
};
ThrowCodeError("unknown_action", { action => $cgi->param('knob') });
} }
elsif ($knob eq 'change_resolution') {
# Fill the resolution field with the correct value.
$cgi->param('resolution', $cgi->param('resolution_knob_change_resolution'));
}
else {
# The resolution field is not in use.
$cgi->delete('resolution');
}
# The action is a valid one.
trick_taint($knob);
# Some information is required for checks.
$vars->{comment_exists} = comment_exists();
$vars->{bug_id} = $cgi->param('id');
$vars->{dup_id} = $cgi->param('dup_id');
$vars->{resolution} = $cgi->param('resolution') || '';
Bugzilla::Bug->check_status_change_triggers($knob, \@idlist, $vars);
# Some triggers require extra actions.
$duplicate = $vars->{dup_id};
$requiremilestone = $vars->{requiremilestone};
DuplicateUserConfirm($vars->{bug_id}, $duplicate) if $vars->{DuplicateUserConfirm};
_remove_remaining_time() if $vars->{remove_remaining_time};
my @keywordlist; my @keywordlist;
my %keywordseen; my %keywordseen;
...@@ -1252,14 +1146,15 @@ foreach my $id (@idlist) { ...@@ -1252,14 +1146,15 @@ foreach my $id (@idlist) {
my $comma = $::comma; my $comma = $::comma;
my $old_bug_obj = new Bugzilla::Bug($id); my $old_bug_obj = new Bugzilla::Bug($id);
my $status; my ($status, $everconfirmed);
my $resolution = $old_bug_obj->resolution; my $resolution = $old_bug_obj->resolution;
# These are the only actions where we care about the resolution field. # We only care about the resolution field if the user explicitly edits it
if ($cgi->param('knob') =~ /^(?:resolve|change_resolution)$/) { # or if he closes the bug.
if ($knob eq 'change_resolution' || $cgi->param('resolution')) {
$resolution = $cgi->param('resolution'); $resolution = $cgi->param('resolution');
} }
($status, $resolution) = ($status, $resolution, $everconfirmed) =
$old_bug_obj->get_new_status_and_resolution(scalar $cgi->param('knob'), $resolution); $old_bug_obj->get_new_status_and_resolution($knob, $resolution);
if ($status ne $old_bug_obj->bug_status) { if ($status ne $old_bug_obj->bug_status) {
$query .= "$comma bug_status = ?"; $query .= "$comma bug_status = ?";
...@@ -1271,6 +1166,11 @@ foreach my $id (@idlist) { ...@@ -1271,6 +1166,11 @@ foreach my $id (@idlist) {
push(@bug_values, $resolution); push(@bug_values, $resolution);
$comma = ','; $comma = ',';
} }
if ($everconfirmed ne $old_bug_obj->everconfirmed) {
$query .= "$comma everconfirmed = ?";
push(@bug_values, $everconfirmed);
$comma = ',';
}
# We have to check whether the bug is moved to another product # We have to check whether the bug is moved to another product
# and/or component before reassigning. If $component is defined, # and/or component before reassigning. If $component is defined,
...@@ -1314,7 +1214,7 @@ foreach my $id (@idlist) { ...@@ -1314,7 +1214,7 @@ foreach my $id (@idlist) {
"user_group_map READ", "group_group_map READ", "flagtypes READ", "user_group_map READ", "group_group_map READ", "flagtypes READ",
"flaginclusions AS i READ", "flagexclusions AS e READ", "flaginclusions AS i READ", "flagexclusions AS e READ",
"keyworddefs READ", "groups READ", "attachments READ", "keyworddefs READ", "groups READ", "attachments READ",
"group_control_map AS oldcontrolmap READ", "bug_status READ", "group_control_map AS oldcontrolmap READ",
"group_control_map AS newcontrolmap READ", "group_control_map AS newcontrolmap READ",
"group_control_map READ", "email_setting READ", "classifications READ"); "group_control_map READ", "email_setting READ", "classifications READ");
......
...@@ -43,6 +43,7 @@ use Bugzilla; ...@@ -43,6 +43,7 @@ use Bugzilla;
use Bugzilla::Constants; use Bugzilla::Constants;
use Bugzilla::Util; use Bugzilla::Util;
use Bugzilla::Error; use Bugzilla::Error;
use Bugzilla::Bug;
eval "use GD"; eval "use GD";
$@ && ThrowCodeError("gd_not_installed"); $@ && ThrowCodeError("gd_not_installed");
......
...@@ -32,6 +32,7 @@ use Bugzilla; ...@@ -32,6 +32,7 @@ use Bugzilla;
use Bugzilla::Constants; use Bugzilla::Constants;
use Bugzilla::Util; use Bugzilla::Util;
use Bugzilla::Error; use Bugzilla::Error;
use Bugzilla::Bug;
########################################################################### ###########################################################################
# General subs # General subs
......
...@@ -50,24 +50,24 @@ ...@@ -50,24 +50,24 @@
<th>&nbsp;</th> <th>&nbsp;</th>
[% FOREACH status = statuses %] [% FOREACH status = statuses %]
<th class="col-header[% status.is_open ? " open-status" : " closed-status" %]"> <th class="col-header[% status.is_open ? " open-status" : " closed-status" %]">
[% status.value FILTER html %] [% status.name FILTER html %]
</th> </th>
[% END %] [% END %]
</tr> </tr>
[%# This defines the entry point in the workflow %] [%# This defines the entry point in the workflow %]
[% p = [{id => 0, value => "{Start}", is_open => 1}] %] [% p = [{id => 0, name => "{Start}", is_open => 1}] %]
[% FOREACH status = p.merge(statuses) %] [% FOREACH status = p.merge(statuses) %]
<tr class="highlight"> <tr class="highlight">
<th align="right" class="[% status.is_open ? "open-status" : "closed-status" %]"> <th align="right" class="[% status.is_open ? "open-status" : "closed-status" %]">
[% status.value FILTER html %] [% status.name FILTER html %]
</th> </th>
[% FOREACH new_status = statuses %] [% FOREACH new_status = statuses %]
[% IF workflow.${status.id}.${new_status.id}.defined %] [% IF workflow.${status.id}.${new_status.id}.defined %]
<td align="center" class="checkbox-cell <td align="center" class="checkbox-cell
[% " checked" IF workflow.${status.id}.${new_status.id} %]" [% " checked" IF workflow.${status.id}.${new_status.id} %]"
title="From [% status.value FILTER html %] to [% new_status.value FILTER html %]"> title="From [% status.name FILTER html %] to [% new_status.name FILTER html %]">
<input type="checkbox" name="c_[% status.id %]_[% new_status.id %]" <input type="checkbox" name="c_[% status.id %]_[% new_status.id %]"
id="c_[% status.id %]_[% new_status.id %]" onclick="toggle_cell(this)" id="c_[% status.id %]_[% new_status.id %]" onclick="toggle_cell(this)"
[% " checked='checked'" IF workflow.${status.id}.${new_status.id} %]> [% " checked='checked'" IF workflow.${status.id}.${new_status.id} %]>
......
...@@ -34,8 +34,12 @@ ...@@ -34,8 +34,12 @@
</script> </script>
<p> <p>
This page allows you to define which status transitions are valid This page allows you to define which status transitions are valid in your workflow.
in your workflow. For compatibility with older versions of [% terms.Bugzilla %], reopening [% terms.abug %]
will only display either UNCONFIRMED or REOPENED (if allowed by your workflow) but not
both. The decision depends on whether the [% terms.bug %] has ever been confirmed or not.
So it is a good idea to allow both transitions and let [% terms.Bugzilla %] select the
correct one.
</p> </p>
<form id="workflow_form" method="POST" action="editworkflow.cgi"> <form id="workflow_form" method="POST" action="editworkflow.cgi">
...@@ -50,24 +54,24 @@ ...@@ -50,24 +54,24 @@
<th>&nbsp;</th> <th>&nbsp;</th>
[% FOREACH status = statuses %] [% FOREACH status = statuses %]
<th class="col-header[% status.is_open ? " open-status" : " closed-status" %]"> <th class="col-header[% status.is_open ? " open-status" : " closed-status" %]">
[% status.value FILTER html %] [% status.name FILTER html %]
</th> </th>
[% END %] [% END %]
</tr> </tr>
[%# This defines the entry point in the workflow %] [%# This defines the entry point in the workflow %]
[% p = [{id => 0, value => "{Start}", is_open => 1}] %] [% p = [{id => 0, name => "{Start}", is_open => 1}] %]
[% FOREACH status = p.merge(statuses) %] [% FOREACH status = p.merge(statuses) %]
<tr class="highlight"> <tr class="highlight">
<th align="right" class="[% status.is_open ? "open-status" : "closed-status" %]"> <th align="right" class="[% status.is_open ? "open-status" : "closed-status" %]">
[% status.value FILTER html %] [% status.name FILTER html %]
</th> </th>
[% FOREACH new_status = statuses %] [% FOREACH new_status = statuses %]
[% IF status.id != new_status.id %] [% IF status.id != new_status.id %]
<td align="center" class="checkbox-cell <td align="center" class="checkbox-cell
[% " checked" IF workflow.${status.id}.${new_status.id}.defined %]" [% " checked" IF workflow.${status.id}.${new_status.id}.defined %]"
title="From [% status.value FILTER html %] to [% new_status.value FILTER html %]"> title="From [% status.name FILTER html %] to [% new_status.name FILTER html %]">
<input type="checkbox" name="w_[% status.id %]_[% new_status.id %]" <input type="checkbox" name="w_[% status.id %]_[% new_status.id %]"
id="w_[% status.id %]_[% new_status.id %]" onclick="toggle_cell(this)" id="w_[% status.id %]_[% new_status.id %]" onclick="toggle_cell(this)"
[% " checked='checked'" IF workflow.${status.id}.${new_status.id}.defined %]> [% " checked='checked'" IF workflow.${status.id}.${new_status.id}.defined %]>
......
...@@ -595,7 +595,7 @@ ...@@ -595,7 +595,7 @@
<td align="right"> <td align="right">
<b><a href="page.cgi?id=fields.html#status">Status</a></b>: <b><a href="page.cgi?id=fields.html#status">Status</a></b>:
</td> </td>
<td>[% status_descs.${bug.bug_status} FILTER html %]</td> <td>[% get_status(bug.bug_status) FILTER html %]</td>
</tr> </tr>
<tr> <tr>
......
...@@ -18,42 +18,39 @@ ...@@ -18,42 +18,39 @@
# #
# Contributor(s): Gervase Markham <gerv@gerv.net> # Contributor(s): Gervase Markham <gerv@gerv.net>
# Vaskin Kissoyan <vkissoyan@yahoo.com> # Vaskin Kissoyan <vkissoyan@yahoo.com>
# Frédéric Buclin <LpSolit@gmail.com>
#%] #%]
[% PROCESS global/variables.none.tmpl %] [% PROCESS global/variables.none.tmpl %]
[%# *** Knob *** %]
<br> <br>
<div id="knob"> <div id="knob">
<div id="knob-options"> <div id="knob-options">
[% knum = 1 %]
[% initial_action_shown = 0 %] [% initial_action_shown = 0 %]
[% IF bug.isunconfirmed && bug.user.canconfirm %] [%# These actions are based on the current custom workflow. %]
[% FOREACH bug_status = bug.status.can_change_to %]
[% NEXT IF bug.isunconfirmed && bug_status.is_open && !bug.user.canconfirm %]
[% NEXT IF bug.isopened && !bug.isunconfirmed && bug_status.is_open && !bug.user.canedit %]
[% NEXT IF !bug_status.is_open && !bug.user.canedit && !bug.user.isreporter %]
[% NEXT IF !bug_status.is_open && bug_status.is_open && !bug.user.canedit && !bug.user.isreporter %]
[%# Special hack to only display UNCO or REOP when reopening, but not both;
# for compatibility with older versions. %]
[% NEXT IF !bug.isopened && (bug.everconfirmed && bug_status.name == "UNCONFIRMED"
|| !bug.everconfirmed && bug_status.name == "REOPENED") %]
[% PROCESS initial_action %] [% PROCESS initial_action %]
<input type="radio" id="knob-confirm" name="knob" value="confirm"> <input type="radio" id="knob_[% bug_status.id FILTER html %]" name="knob"
<label for="knob-confirm"> value="[% bug_status.name FILTER html %]">
Confirm [% terms.bug %] (change status to <b>[% get_status("NEW") FILTER html %]</b>) <label for="knob_[% bug_status.id FILTER html %]">
Change status to <b>[% get_status(bug_status.name) FILTER html %]</b>
</label> </label>
<br> [% IF bug.isopened && !bug_status.is_open %]
[% knum = knum + 1 %] and set the resolution to [% PROCESS select_resolution field = "knob_${bug_status.id}" %]
[% END %] [% END %]
[% IF bug.isopened && bug.bug_status != "ASSIGNED" && bug.user.canedit
&& (!bug.isunconfirmed || bug.user.canconfirm) %]
[% PROCESS initial_action %]
<input type="radio" id="knob-accept" name="knob" value="accept">
<label for="knob-accept">
Accept [% terms.bug %] (
[% IF bug.isunconfirmed %]confirm [% terms.bug %], and [% END %]change
status to <b>[% get_status("ASSIGNED") FILTER html %]</b>)
</label>
<br> <br>
[% knum = knum + 1 %]
[% END %] [% END %]
[%# These actions are special and are independent of the workflow. %]
[% IF bug.user.canedit || bug.user.isreporter %] [% IF bug.user.canedit || bug.user.isreporter %]
[% IF bug.isopened %] [% IF bug.isopened %]
[% IF bug.resolution %] [% IF bug.resolution %]
...@@ -64,65 +61,27 @@ ...@@ -64,65 +61,27 @@
<b>[% get_resolution(bug.resolution) FILTER html %]</b>) <b>[% get_resolution(bug.resolution) FILTER html %]</b>)
</label> </label>
<br> <br>
[% knum = knum + 1 %]
[% END %] [% END %]
[% PROCESS initial_action %]
<input type="radio" id="knob-resolve" name="knob" value="resolve">
<label for="knob-resolve">
Resolve [% terms.bug %], changing
<a href="page.cgi?id=fields.html#resolution">resolution</a> to
</label>
[% PROCESS select_resolution %]
[% PROCESS duplicate %]
[% ELSE %] [% ELSE %]
[% IF bug.resolution != "MOVED" || [% IF bug.resolution != "MOVED" || bug.user.canmove %]
(bug.resolution == "MOVED" && bug.user.canmove) %]
[% PROCESS initial_action %] [% PROCESS initial_action %]
<input type="radio" id="knob-change-resolution" name="knob" value="change_resolution"> <input type="radio" id="knob_change_resolution" name="knob" value="change_resolution">
<label for="knob-change-resolution"> <label for="knob_change_resolution">
Change <a href="page.cgi?id=fields.html#resolution">resolution</a> to Change <a href="page.cgi?id=fields.html#resolution">resolution</a> to
</label> </label>
[% PROCESS select_resolution %] [% PROCESS select_resolution field = "knob_change_resolution" %]
[% PROCESS duplicate %]
<input type="radio" id="knob-reopen" name="knob" value="reopen">
<label for="knob-reopen">
Reopen [% terms.bug %]
</label>
<br> <br>
[% knum = knum + 1 %]
[% END %]
[% IF bug.bug_status == "RESOLVED" %]
[% PROCESS initial_action %]
<input type="radio" id="knob-verify" name="knob" value="verify">
<label for="knob-verify">
Mark [% terms.bug %] as <b>[% get_status("VERIFIED") FILTER html %]</b>
</label>
<br>
[% knum = knum + 1 %]
[% END %]
[% IF bug.bug_status != "CLOSED" %]
[% PROCESS initial_action %]
<input type="radio" id="knob-close" name="knob" value="close">
<label for="knob-close">
Mark [% terms.bug %] as <b>[% get_status("CLOSED") FILTER html %]</b>
</label>
<br>
[% knum = knum + 1 %]
[% END %] [% END %]
[% END %] [% END %]
[% PROCESS duplicate %]
[% END %] [% END %]
</div> </div>
<div id="knob-buttons"> <div id="knob-buttons">
<input type="submit" value="Commit" id="commit"> <input type="submit" value="Commit" id="commit">
[% IF bug.user.canmove %] [% IF bug.user.canmove %]
&nbsp; <font size="+1"><b> | </b></font> &nbsp; <input type="submit" name="action" id="action" value="[% Param("move-button-text") %]">
<input type="submit" name="action" id="action"
value="[% Param("move-button-text") %]">
[% END %] [% END %]
</div> </div>
</div> </div>
...@@ -143,23 +102,20 @@ ...@@ -143,23 +102,20 @@
[% END %] [% END %]
[% BLOCK select_resolution %] [% BLOCK select_resolution %]
<select name="resolution" <select name="resolution_[% field FILTER html %]"
onchange="document.changeform.knob[[% knum %]].checked=true"> onchange="document.forms['changeform'].[% field FILTER html %].checked=true">
[% FOREACH r = bug.choices.resolution %] [% FOREACH r = bug.choices.resolution %]
<option value="[% r FILTER html %]">[% get_resolution(r) FILTER html %]</option> <option value="[% r FILTER html %]">[% get_resolution(r) FILTER html %]</option>
[% END %] [% END %]
</select> </select>
<br>
[% knum = knum + 1 %]
[% END %] [% END %]
[% BLOCK duplicate %] [% BLOCK duplicate %]
<input type="radio" id="knob-duplicate" name="knob" value="duplicate"> <input type="radio" id="knob_duplicate" name="knob" value="duplicate">
<label for="knob-duplicate"> <label for="knob_duplicate">
Mark the [% terms.bug %] as duplicate of [% terms.bug %] # Mark the [% terms.bug %] as duplicate of [% terms.bug %] #
</label> </label>
<input name="dup_id" size="6" <input name="dup_id" size="6"
onchange="if (this.value != '') {document.changeform.knob[[% knum %]].checked=true}"> onchange="if (this.value != '') {document.forms['changeform'].knob_duplicate.checked=true}">
<br> <br>
[% knum = knum + 1 %]
[% END %] [% END %]
...@@ -191,7 +191,6 @@ ...@@ -191,7 +191,6 @@
'list/edit-multiple.html.tmpl' => [ 'list/edit-multiple.html.tmpl' => [
'group.id', 'group.id',
'knum',
'menuname', 'menuname',
], ],
...@@ -319,10 +318,6 @@ ...@@ -319,10 +318,6 @@
'flag.status', 'flag.status',
], ],
'bug/knob.html.tmpl' => [
'knum',
],
'bug/navigate.html.tmpl' => [ 'bug/navigate.html.tmpl' => [
'bug_list.first', 'bug_list.first',
'bug_list.last', 'bug_list.last',
......
...@@ -243,7 +243,13 @@ ...@@ -243,7 +243,13 @@
[% ELSIF error == "comment_required" %] [% ELSIF error == "comment_required" %]
[% title = "Comment Required" %] [% title = "Comment Required" %]
You have to specify a <b>comment</b> on this change. You have to specify a <b>comment</b>
[% IF old.size && new %]
to change the [% terms.bug %] status from [% old.join(", ") FILTER html %]
to [% new FILTER html %].
[% ELSE %]
on this change.
[% END %]
Please explain your change. Please explain your change.
[% ELSIF error == "comment_too_long" %] [% ELSIF error == "comment_too_long" %]
...@@ -634,6 +640,11 @@ ...@@ -634,6 +640,11 @@
The only legal values for the <em>Attachment is patch</em> field are The only legal values for the <em>Attachment is patch</em> field are
0 and 1. 0 and 1.
[% ELSIF error == "illegal_bug_status_transition" %]
[% title = "Illegal $terms.Bug Status Change" %]
You are not allowed to change the [% terms.bug %] status from
[%+ old.join(", ") FILTER html %] to [%+ new FILTER html %].
[% ELSIF error == "illegal_change" %] [% ELSIF error == "illegal_change" %]
[% title = "Not allowed" %] [% title = "Not allowed" %]
You tried to change the You tried to change the
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
# #
# Contributor(s): Myk Melez <myk@mozilla.org> # Contributor(s): Myk Melez <myk@mozilla.org>
# Max Kanat-Alexander <mkanat@bugzilla.org> # Max Kanat-Alexander <mkanat@bugzilla.org>
# Frédéric Buclin <LpSolit@gmail.com>
#%] #%]
[% PROCESS global/variables.none.tmpl %] [% PROCESS global/variables.none.tmpl %]
...@@ -301,66 +302,25 @@ ...@@ -301,66 +302,25 @@
[% knum = 0 %]
<input id="knob-none" type="radio" name="knob" value="none" checked="checked"> <input id="knob-none" type="radio" name="knob" value="none" checked="checked">
<label for="knob-none">Do nothing else</label><br> <label for="knob-none">Do nothing else</label><br>
[% IF bugstatuses.size == 1 && bugstatuses.0 == unconfirmedstate %] [% FOREACH bug_status = new_bug_statuses %]
[% knum = knum + 1 %] <input type="radio" id="knob_[% bug_status.id FILTER html %]" name="knob"
<input id="knob-confirm" type="radio" name="knob" value="confirm"> value="[% bug_status.name FILTER html %]">
<label for="knob-confirm"> <label for="knob_[% bug_status.id FILTER html %]">
Confirm [% terms.bugs %] (change status to <b>[% get_status("NEW") FILTER html %]</b>) Change status to <b>[% get_status(bug_status.name) FILTER html %]</b>
</label><br>
[% END %]
[%# If all the bugs being changed are open, allow the user to accept them,
clear their resolution or resolve them. %]
[% IF !bugstatuses.containsany(closedstates) %]
[% knum = knum + 1 %]
<input id="knob-accept" type="radio" name="knob" value="accept">
<label for="knob-accept">
Accept [% terms.bugs %] (change status to <b>[% get_status("ASSIGNED") FILTER html %]</b>)
</label><br>
[% knum = knum + 1 %]
<input id="knob-clearresolution" type="radio" name="knob" value="clearresolution">
<label for="knob-clearresolution">Clear the resolution</label><br>
[% knum = knum + 1 %]
<input id="knob-resolve" type="radio" name="knob" value="resolve">
<label for="knob-resolve">
Resolve [% terms.bugs %], changing <a href="page.cgi?id=fields.html#resolution">resolution</a> to
</label> </label>
<select name="resolution" onchange="document.forms.changeform.knob[[% knum %]].checked=true"> [% IF !bug_status.is_open %]
[% FOREACH resolution = resolutions %] and set the resolution to [% PROCESS select_resolution field = "knob_${bug_status.id}" %]
[% NEXT IF !resolution %]
<option value="[% resolution FILTER html %]">
[% get_resolution(resolution) FILTER html %]
</option>
[% END %] [% END %]
</select><br> <br>
[% END %]
[%# If all the bugs are closed, allow the user to reopen them. %]
[% IF !bugstatuses.containsany(openstates) %]
[% knum = knum + 1 %]
<input id="knob-reopen" type="radio" name="knob" value="reopen">
<label for="knob-reopen">Reopen [% terms.bugs %]</label><br>
[% END %] [% END %]
[% IF bugstatuses.size == 1 %] [%# If all the bugs being changed are open, allow the user to clear their resolution. %]
[% IF bugstatuses.contains('RESOLVED') %] [% IF !current_bug_statuses.containsany(closedstates) %]
[% knum = knum + 1 %] <input id="knob-clearresolution" type="radio" name="knob" value="clearresolution">
<input id="knob-verify" type="radio" name="knob" value="verify"> <label for="knob-clearresolution">Clear the resolution</label><br>
<label for="knob-verify">Mark [% terms.bugs %] as <b>[% get_status("VERIFIED") FILTER html %]</b></label><br>
[% END %]
[% END %]
[% IF !bugstatuses.containsany(openstates) AND !bugstatuses.contains('CLOSED') %]
[% knum = knum + 1 %]
<input id="knob-close" type="radio" name="knob" value="close">
<label for="knob-close">Mark [% terms.bugs %] as <b>[% get_status("CLOSED") FILTER html %]</b></label><br>
[% END %] [% END %]
<input type="submit" id="commit" value="Commit"> <input type="submit" id="commit" value="Commit">
...@@ -384,3 +344,13 @@ ...@@ -384,3 +344,13 @@
[% END %] [% END %]
</select> </select>
[% END %] [% END %]
[% BLOCK select_resolution %]
<select name="resolution"
onchange="document.forms['changeform'].[% field FILTER html %].checked=true">
[% FOREACH r = resolutions %]
[% NEXT IF !r %]
<option value="[% r FILTER html %]">[% get_resolution(r) FILTER html %]</option>
[% END %]
</select>
[% END %]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment