summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xBugzilla/Bug.pm8
-rw-r--r--Bugzilla/Config/BugChange.pm22
-rw-r--r--Bugzilla/Config/Common.pm12
-rw-r--r--Bugzilla/Install/DB.pm153
-rw-r--r--Bugzilla/Status.pm55
-rwxr-xr-xeditparams.cgi4
-rwxr-xr-xeditvalues.cgi14
-rw-r--r--editworkflow.cgi5
-rwxr-xr-xprocess_bug.cgi5
-rw-r--r--template/en/default/admin/params/bugchange.html.tmpl3
-rw-r--r--template/en/default/admin/workflow/edit.html.tmpl19
-rw-r--r--template/en/default/bug/knob.html.tmpl1
12 files changed, 217 insertions, 84 deletions
diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm
index 3ace277dd..da9e9a6b3 100755
--- a/Bugzilla/Bug.pm
+++ b/Bugzilla/Bug.pm
@@ -1986,10 +1986,10 @@ sub get_new_status_and_resolution {
# Leaving the status unchanged doesn't need more investigation.
return ($self->bug_status, $self->resolution, $self->everconfirmed);
}
- 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 'duplicate' || $action eq 'move') {
+ # Always change the bug status, even if the bug was already "closed".
+ $status = Bugzilla->params->{'duplicate_or_move_bug_status'};
+ $resolution = ($action eq 'duplicate') ? 'DUPLICATE' : 'MOVED';
}
elsif ($action eq 'change_resolution') {
$status = $self->bug_status;
diff --git a/Bugzilla/Config/BugChange.pm b/Bugzilla/Config/BugChange.pm
index 6941f0046..65b2aec96 100644
--- a/Bugzilla/Config/BugChange.pm
+++ b/Bugzilla/Config/BugChange.pm
@@ -34,13 +34,35 @@ package Bugzilla::Config::BugChange;
use strict;
use Bugzilla::Config::Common;
+use Bugzilla::Status;
$Bugzilla::Config::BugChange::sortkey = "03";
sub get_param_list {
my $class = shift;
+
+ # Hardcoded bug statuses which existed before Bugzilla 3.1.
+ my @closed_bug_statuses = ('RESOLVED', 'VERIFIED', 'CLOSED');
+
+ # If we are upgrading from 3.0 or older, bug statuses are not customisable
+ # and bug_status.is_open is not yet defined (hence the eval), so we use
+ # the bug statuses above as they are still hardcoded.
+ eval {
+ my @current_closed_states = map {$_->name} Bugzilla::Status::closed_bug_statuses();
+ # If no closed state was found, use the default list above.
+ @closed_bug_statuses = @current_closed_states if scalar(@current_closed_states);
+ };
+
my @param_list = (
{
+ name => 'duplicate_or_move_bug_status',
+ type => 's',
+ choices => \@closed_bug_statuses,
+ default => $closed_bug_statuses[0],
+ checker => \&check_bug_status
+ },
+
+ {
name => 'letsubmitterchoosepriority',
type => 'b',
default => 1
diff --git a/Bugzilla/Config/Common.pm b/Bugzilla/Config/Common.pm
index 0d6db5257..188ef0c90 100644
--- a/Bugzilla/Config/Common.pm
+++ b/Bugzilla/Config/Common.pm
@@ -40,6 +40,7 @@ use Bugzilla::Util;
use Bugzilla::Constants;
use Bugzilla::Field;
use Bugzilla::Group;
+use Bugzilla::Status;
use base qw(Exporter);
@Bugzilla::Config::Common::EXPORT =
@@ -48,7 +49,7 @@ use base qw(Exporter);
check_opsys check_shadowdb check_urlbase check_webdotbase
check_netmask check_user_verify_class check_image_converter
check_languages check_mail_delivery_method check_notification
- check_timezone check_utf8
+ check_timezone check_utf8 check_bug_status
);
# Checking functions for the various values
@@ -166,6 +167,15 @@ sub check_opsys {
return "";
}
+sub check_bug_status {
+ my $bug_status = shift;
+ my @closed_bug_statuses = map {$_->name} Bugzilla::Status::closed_bug_statuses();
+ if (lsearch(\@closed_bug_statuses, $bug_status) < 0) {
+ return "Must be a valid closed status: one of " . join(', ', @closed_bug_statuses);
+ }
+ return "";
+}
+
sub check_group {
my $group_name = shift;
return "" unless $group_name;
diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm
index 3991cd453..4b4e7a068 100644
--- a/Bugzilla/Install/DB.pm
+++ b/Bugzilla/Install/DB.pm
@@ -2783,85 +2783,102 @@ sub _initialize_workflow {
my $old_params = shift;
my $dbh = Bugzilla->dbh;
- if (!$dbh->bz_column_info('bug_status', 'is_open')) {
- $dbh->bz_add_column('bug_status', 'is_open',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
-
- # Till now, bug statuses were not customizable. Nevertheless, local
- # changes are possible and so we will try to respect these changes.
- # This means: get the status of bugs having a resolution different from ''
- # and mark these statuses as 'closed', even if some of these statuses are
- # expected to be open statuses. Bug statuses we have no information about
- # are left as 'open'.
- my @statuses =
- @{$dbh->selectcol_arrayref('SELECT DISTINCT bug_status FROM bugs
- WHERE resolution != ?', undef, '')};
+ $dbh->bz_add_column('bug_status', 'is_open',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
- # Append the default list of closed statuses. Duplicated statuses don't hurt.
- @statuses = map {$dbh->quote($_)} (@statuses, qw(RESOLVED VERIFIED CLOSED));
+ # Till now, bug statuses were not customizable. Nevertheless, local
+ # changes are possible and so we will try to respect these changes.
+ # This means: get the status of bugs having a resolution different from ''
+ # and mark these statuses as 'closed', even if some of these statuses are
+ # expected to be open statuses. Bug statuses we have no information about
+ # are left as 'open'.
+ my @closed_statuses =
+ @{$dbh->selectcol_arrayref('SELECT DISTINCT bug_status FROM bugs
+ WHERE resolution != ?', undef, '')};
+
+ # Append the default list of closed statuses *unless* we detect at least
+ # one closed state in the DB (i.e. with is_open = 0). This would mean that
+ # the DB has already been updated at least once and maybe the admin decided
+ # that e.g. 'RESOLVED' is now an open state, in which case we don't want to
+ # override this attribute. At least one bug status has to be a closed state
+ # anyway (due to the 'duplicate_or_move_bug_status' parameter) so it's safe
+ # to use this criteria.
+ my $num_closed_states = $dbh->selectrow_array('SELECT COUNT(*) FROM bug_status
+ WHERE is_open = 0');
+
+ if (!$num_closed_states) {
+ @closed_statuses =
+ map {$dbh->quote($_)} (@closed_statuses, qw(RESOLVED VERIFIED CLOSED));
print "Marking closed bug statuses as such...\n";
$dbh->do('UPDATE bug_status SET is_open = 0 WHERE value IN (' .
- join(', ', @statuses) . ')');
+ join(', ', @closed_statuses) . ')');
}
# Populate the status_workflow table. We do nothing if the table already
# has entries. If all bug status transitions have been deleted, the
# workflow will be restored to its default schema.
my $count = $dbh->selectrow_array('SELECT COUNT(*) FROM status_workflow');
- return if $count;
-
- my $create = $old_params->{'commentoncreate'};
- my $confirm = $old_params->{'commentonconfirm'};
- my $accept = $old_params->{'commentonaccept'};
- my $resolve = $old_params->{'commentonresolve'};
- my $verify = $old_params->{'commentonverify'};
- my $close = $old_params->{'commentonclose'};
- my $reopen = $old_params->{'commentonreopen'};
- # This was till recently the only way to get back to NEW for
- # confirmed bugs, so we use this parameter here.
- my $reassign = $old_params->{'commentonreassign'};
-
- # This is the default workflow.
- my @workflow = ([undef, 'UNCONFIRMED', $create],
- [undef, 'NEW', $create],
- [undef, 'ASSIGNED', $create],
- ['UNCONFIRMED', 'NEW', $confirm],
- ['UNCONFIRMED', 'ASSIGNED', $accept],
- ['UNCONFIRMED', 'RESOLVED', $resolve],
- ['NEW', 'ASSIGNED', $accept],
- ['NEW', 'RESOLVED', $resolve],
- ['ASSIGNED', 'NEW', $reassign],
- ['ASSIGNED', 'RESOLVED', $resolve],
- ['REOPENED', 'NEW', $reassign],
- ['REOPENED', 'ASSIGNED', $accept],
- ['REOPENED', 'RESOLVED', $resolve],
- ['RESOLVED', 'UNCONFIRMED', $reopen],
- ['RESOLVED', 'REOPENED', $reopen],
- ['RESOLVED', 'VERIFIED', $verify],
- ['RESOLVED', 'CLOSED', $close],
- ['VERIFIED', 'UNCONFIRMED', $reopen],
- ['VERIFIED', 'REOPENED', $reopen],
- ['VERIFIED', 'CLOSED', $close],
- ['CLOSED', 'UNCONFIRMED', $reopen],
- ['CLOSED', 'REOPENED', $reopen]);
-
- print "Now filling the 'status_workflow' table with valid bug status transitions...\n";
- my $sth_select = $dbh->prepare('SELECT id FROM bug_status WHERE value = ?');
- my $sth = $dbh->prepare('INSERT INTO status_workflow (old_status, new_status,
- require_comment) VALUES (?, ?, ?)');
-
- foreach my $transition (@workflow) {
- my ($from, $to);
- # If it's an initial state, there is no "old" value.
- $from = $dbh->selectrow_array($sth_select, undef, $transition->[0])
- if $transition->[0];
- $to = $dbh->selectrow_array($sth_select, undef, $transition->[1]);
- # If one of the bug statuses doesn't exist, the transition is invalid.
- next if (($transition->[0] && !$from) || !$to);
-
- $sth->execute($from, $to, $transition->[2] ? 1 : 0);
+
+ if (!$count) {
+ # Make sure the variables below are defined as
+ # status_workflow.require_comment cannot be NULL.
+ my $create = $old_params->{'commentoncreate'} || 0;
+ my $confirm = $old_params->{'commentonconfirm'} || 0;
+ my $accept = $old_params->{'commentonaccept'} || 0;
+ my $resolve = $old_params->{'commentonresolve'} || 0;
+ my $verify = $old_params->{'commentonverify'} || 0;
+ my $close = $old_params->{'commentonclose'} || 0;
+ my $reopen = $old_params->{'commentonreopen'} || 0;
+ # This was till recently the only way to get back to NEW for
+ # confirmed bugs, so we use this parameter here.
+ my $reassign = $old_params->{'commentonreassign'} || 0;
+
+ # This is the default workflow.
+ my @workflow = ([undef, 'UNCONFIRMED', $create],
+ [undef, 'NEW', $create],
+ [undef, 'ASSIGNED', $create],
+ ['UNCONFIRMED', 'NEW', $confirm],
+ ['UNCONFIRMED', 'ASSIGNED', $accept],
+ ['UNCONFIRMED', 'RESOLVED', $resolve],
+ ['NEW', 'ASSIGNED', $accept],
+ ['NEW', 'RESOLVED', $resolve],
+ ['ASSIGNED', 'NEW', $reassign],
+ ['ASSIGNED', 'RESOLVED', $resolve],
+ ['REOPENED', 'NEW', $reassign],
+ ['REOPENED', 'ASSIGNED', $accept],
+ ['REOPENED', 'RESOLVED', $resolve],
+ ['RESOLVED', 'UNCONFIRMED', $reopen],
+ ['RESOLVED', 'REOPENED', $reopen],
+ ['RESOLVED', 'VERIFIED', $verify],
+ ['RESOLVED', 'CLOSED', $close],
+ ['VERIFIED', 'UNCONFIRMED', $reopen],
+ ['VERIFIED', 'REOPENED', $reopen],
+ ['VERIFIED', 'CLOSED', $close],
+ ['CLOSED', 'UNCONFIRMED', $reopen],
+ ['CLOSED', 'REOPENED', $reopen]);
+
+ print "Now filling the 'status_workflow' table with valid bug status transitions...\n";
+ my $sth_select = $dbh->prepare('SELECT id FROM bug_status WHERE value = ?');
+ my $sth = $dbh->prepare('INSERT INTO status_workflow (old_status, new_status,
+ require_comment) VALUES (?, ?, ?)');
+
+ foreach my $transition (@workflow) {
+ my ($from, $to);
+ # If it's an initial state, there is no "old" value.
+ $from = $dbh->selectrow_array($sth_select, undef, $transition->[0])
+ if $transition->[0];
+ $to = $dbh->selectrow_array($sth_select, undef, $transition->[1]);
+ # If one of the bug statuses doesn't exist, the transition is invalid.
+ next if (($transition->[0] && !$from) || !$to);
+
+ $sth->execute($from, $to, $transition->[2] ? 1 : 0);
+ }
}
+
+ # Make sure the bug status used by the 'duplicate_or_move_bug_status'
+ # parameter has all the required transitions set.
+ Bugzilla::Status::add_missing_bug_status_transitions();
}
1;
diff --git a/Bugzilla/Status.pm b/Bugzilla/Status.pm
index e91f83871..cf8f98efa 100644
--- a/Bugzilla/Status.pm
+++ b/Bugzilla/Status.pm
@@ -54,15 +54,22 @@ sub is_open { return $_[0]->{'is_open'}; }
##### Methods ####
###############################
+sub closed_bug_statuses {
+ my @bug_statuses = Bugzilla::Status->get_all;
+ @bug_statuses = grep { !$_->is_open } @bug_statuses;
+ return @bug_statuses;
+}
+
sub can_change_to {
my $self = shift;
my $dbh = Bugzilla->dbh;
if (!ref($self) || !defined $self->{'can_change_to'}) {
- my ($cond, @args);
+ my ($cond, @args, $self_exists);
if (ref($self)) {
$cond = '= ?';
push(@args, $self->id);
+ $self_exists = 1;
}
else {
$cond = 'IS NULL';
@@ -78,12 +85,37 @@ sub can_change_to {
AND old_status $cond",
undef, @args);
+ # Allow the bug status to remain unchanged.
+ push(@$new_status_ids, $self->id) if $self_exists;
$self->{'can_change_to'} = Bugzilla::Status->new_from_list($new_status_ids);
}
return $self->{'can_change_to'};
}
+sub add_missing_bug_status_transitions {
+ my $bug_status = shift || Bugzilla->params->{'duplicate_or_move_bug_status'};
+ my $dbh = Bugzilla->dbh;
+ my $new_status = new Bugzilla::Status({name => $bug_status});
+ # Silently discard invalid bug statuses.
+ $new_status || return;
+
+ my $missing_statuses = $dbh->selectcol_arrayref('SELECT id
+ FROM bug_status
+ LEFT JOIN status_workflow
+ ON old_status = id
+ AND new_status = ?
+ WHERE old_status IS NULL',
+ undef, $new_status->id);
+
+ my $sth = $dbh->prepare('INSERT INTO status_workflow
+ (old_status, new_status) VALUES (?, ?)');
+
+ foreach my $old_status_id (@$missing_statuses) {
+ next if ($old_status_id == $new_status->id);
+ $sth->execute($old_status_id, $new_status->id);
+ }
+}
1;
@@ -100,6 +132,10 @@ Bugzilla::Status - Bug status class.
my $bug_status = new Bugzilla::Status({name => 'ASSIGNED'});
my $bug_status = new Bugzilla::Status(4);
+ my @closed_bug_statuses = Bugzilla::Status::closed_bug_statuses();
+
+ Bugzilla::Status::add_missing_bug_status_transitions($bug_status);
+
=head1 DESCRIPTION
Status.pm represents a bug status object. It is an implementation
@@ -113,6 +149,15 @@ below.
=over
+=item C<closed_bug_statuses>
+
+ Description: Returns a list of C<Bugzilla::Status> objects which can have
+ a resolution associated with them ("closed" bug statuses).
+
+ Params: none.
+
+ Returns: A list of Bugzilla::Status objects.
+
=item C<can_change_to>
Description: Returns the list of active statuses a bug can be changed to
@@ -122,6 +167,14 @@ below.
Returns: A list of Bugzilla::Status objects.
+=item C<add_missing_bug_status_transitions>
+
+ Description: Insert all missing transitions to a given bug status.
+
+ Params: $bug_status - The value (name) of a bug status.
+
+ Returns: nothing.
+
=back
=cut
diff --git a/editparams.cgi b/editparams.cgi
index 7d280474b..38d486656 100755
--- a/editparams.cgi
+++ b/editparams.cgi
@@ -34,6 +34,7 @@ use Bugzilla::Error;
use Bugzilla::Token;
use Bugzilla::User;
use Bugzilla::User::Setting;
+use Bugzilla::Status;
my $user = Bugzilla->login(LOGIN_REQUIRED);
my $cgi = Bugzilla->cgi;
@@ -137,6 +138,9 @@ if ($action eq 'save' && $current_module) {
if ($name eq 'languages') {
$update_lang_user_pref = 1;
}
+ if ($name eq 'duplicate_or_move_bug_status') {
+ Bugzilla::Status::add_missing_bug_status_transitions($value);
+ }
}
}
if ($update_lang_user_pref) {
diff --git a/editvalues.cgi b/editvalues.cgi
index 3974d45dc..951078389 100755
--- a/editvalues.cgi
+++ b/editvalues.cgi
@@ -29,6 +29,7 @@ use Bugzilla::Config qw(:admin);
use Bugzilla::Token;
use Bugzilla::Field;
use Bugzilla::Bug;
+use Bugzilla::Status;
# List of different tables that contain the changeable field values
# (the old "enums.") Keep them in alphabetical order by their
@@ -136,7 +137,7 @@ $defaults{'bug_severity'} = 'defaultseverity';
# Alternatively, a list of non-editable values can be specified.
# In this case, only the sortkey can be altered.
my %static;
-$static{'bug_status'} = ['UNCONFIRMED'];
+$static{'bug_status'} = ['UNCONFIRMED', Bugzilla->params->{'duplicate_or_move_bug_status'}];
$static{'resolution'} = ['', 'FIXED', 'MOVED', 'DUPLICATE'];
$static{$_->name} = ['---'] foreach (@custom_fields);
@@ -234,9 +235,14 @@ if ($action eq 'new') {
$dbh->do("INSERT INTO $field (value, sortkey) VALUES (?, ?)",
undef, ($value, $sortkey));
- if ($field eq 'bug_status' && !$cgi->param('is_open')) {
- # The bug status is a closed state, but they are open by default.
- $dbh->do('UPDATE bug_status SET is_open = 0 WHERE value = ?', undef, $value);
+ if ($field eq 'bug_status') {
+ unless ($cgi->param('is_open')) {
+ # The bug status is a closed state, but they are open by default.
+ $dbh->do('UPDATE bug_status SET is_open = 0 WHERE value = ?', undef, $value);
+ }
+ # Allow the transition from this new bug status to the one used
+ # by the 'duplicate_or_move_bug_status' parameter.
+ Bugzilla::Status::add_missing_bug_status_transitions();
}
delete_token($token);
diff --git a/editworkflow.cgi b/editworkflow.cgi
index ac914f76d..6aaed345e 100644
--- a/editworkflow.cgi
+++ b/editworkflow.cgi
@@ -100,7 +100,10 @@ elsif ($action eq 'update') {
foreach my $new (@$statuses) {
next if $old->id == $new->id;
- if ($cgi->param('w_' . $old->id . '_' . $new->id)) {
+ # All transitions to 'duplicate_or_move_bug_status' must be valid.
+ if ($cgi->param('w_' . $old->id . '_' . $new->id)
+ || ($new->name eq Bugzilla->params->{'duplicate_or_move_bug_status'}))
+ {
$sth_insert->execute($old->id, $new->id)
unless defined $workflow->{$old->id}->{$new->id};
}
diff --git a/process_bug.cgi b/process_bug.cgi
index 6abbbb401..0faae9e7c 100755
--- a/process_bug.cgi
+++ b/process_bug.cgi
@@ -496,8 +496,9 @@ if ($action eq Bugzilla->params->{'move-button-text'}) {
local $Storable::forgive_me = 1;
my $bugs = dclone(\@bug_objects);
foreach my $bug (@bug_objects) {
- $bug->set_status('RESOLVED');
- $bug->set_resolution('MOVED');
+ my ($status, $resolution) = $bug->get_new_status_and_resolution('move');
+ $bug->set_status($status);
+ $bug->set_resolution($resolution);
}
$_->update() foreach @bug_objects;
$dbh->bz_unlock_tables();
diff --git a/template/en/default/admin/params/bugchange.html.tmpl b/template/en/default/admin/params/bugchange.html.tmpl
index 022e0cac1..0b2f42c6d 100644
--- a/template/en/default/admin/params/bugchange.html.tmpl
+++ b/template/en/default/admin/params/bugchange.html.tmpl
@@ -25,6 +25,9 @@
%]
[% param_descs = {
+ duplicate_or_move_bug_status => "When $terms.abug is marked as a duplicate of another one " _
+ "or is moved to another installation, use this $terms.bug status."
+
letsubmitterchoosepriority => "If this is on, then people submitting $terms.bugs can " _
"choose an initial priority for that ${terms.bug}. " _
"If off, then all $terms.bugs initially have the default " _
diff --git a/template/en/default/admin/workflow/edit.html.tmpl b/template/en/default/admin/workflow/edit.html.tmpl
index d602171a1..68e16a022 100644
--- a/template/en/default/admin/workflow/edit.html.tmpl
+++ b/template/en/default/admin/workflow/edit.html.tmpl
@@ -69,12 +69,14 @@
[% FOREACH new_status = statuses %]
[% IF status.id != new_status.id %]
- <td align="center" class="checkbox-cell
- [% " checked" IF workflow.${status.id}.${new_status.id}.defined %]"
+ [% checked = workflow.${status.id}.${new_status.id}.defined ? 1 : 0 %]
+ [% mandatory = (status.id && new_status.name == Param("duplicate_or_move_bug_status")) ? 1 : 0 %]
+ <td align="center" class="checkbox-cell[% " checked" IF checked || mandatory %]"
title="From [% status.name FILTER html %] to [% new_status.name FILTER html %]">
<input type="checkbox" name="w_[% status.id %]_[% new_status.id %]"
id="w_[% status.id %]_[% new_status.id %]" onclick="toggle_cell(this)"
- [% " checked='checked'" IF workflow.${status.id}.${new_status.id}.defined %]>
+ [%+ "checked='checked'" IF checked || mandatory %]
+ [%+ "disabled='disabled'" IF mandatory %]>
</td>
[% ELSE %]
<td class="checkbox-cell forbidden">&nbsp;</td>
@@ -84,6 +86,17 @@
[% END %]
</table>
+<p>
+ When [% terms.abug %] is marked as a duplicate of another one or is moved
+ to another installation, the [% terms.bug %] status is automatically set to
+ <b>[% Param("duplicate_or_move_bug_status") FILTER html %]</b>. All transitions to
+ this [% terms.bug %] status must then be valid (this is the reason why you cannot edit
+ them above).<br>
+ Note: you can change this setting by visiting the
+ <a href="editparams.cgi?section=bugchange#duplicate_or_move_bug_status">Parameters</a>
+ page and editing the <i>duplicate_or_move_bug_status</i> parameter.
+</p>
+
<p align="center">
<input type="hidden" name="action" value="update">
<input type="hidden" name="token" value="[% token FILTER html %]">
diff --git a/template/en/default/bug/knob.html.tmpl b/template/en/default/bug/knob.html.tmpl
index 99aed9c22..257ce4d94 100644
--- a/template/en/default/bug/knob.html.tmpl
+++ b/template/en/default/bug/knob.html.tmpl
@@ -39,6 +39,7 @@
[% NEXT IF !bug.isopened && (bug.everconfirmed && bug_status.name == "UNCONFIRMED"
|| !bug.everconfirmed && bug_status.name == "REOPENED") %]
[% PROCESS initial_action %]
+ [% NEXT IF bug_status.name == bug.bug_status %]
<input type="radio" id="knob_[% bug_status.id FILTER html %]" name="knob"
value="[% bug_status.name FILTER html %]">
<label for="knob_[% bug_status.id FILTER html %]">