]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 385415: Bugs marked as duplicate or moved to another installation always go to...
authorlpsolit%gmail.com <>
Fri, 13 Jul 2007 18:10:39 +0000 (18:10 +0000)
committerlpsolit%gmail.com <>
Fri, 13 Jul 2007 18:10:39 +0000 (18:10 +0000)
12 files changed:
Bugzilla/Bug.pm
Bugzilla/Config/BugChange.pm
Bugzilla/Config/Common.pm
Bugzilla/Install/DB.pm
Bugzilla/Status.pm
editparams.cgi
editvalues.cgi
editworkflow.cgi
process_bug.cgi
template/en/default/admin/params/bugchange.html.tmpl
template/en/default/admin/workflow/edit.html.tmpl
template/en/default/bug/knob.html.tmpl

index 3ace277dd79341fe219ba6342430f4787082af4c..da9e9a6b3964529eb13142ddb894320c9e5fa26a 100755 (executable)
@@ -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;
index 6941f00469336264dedc5348f33d5eb873cb3c47..65b2aec964f3bd7fe752796c1df2f5602b03282c 100644 (file)
@@ -34,12 +34,34 @@ 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',
index 0d6db5257e47219d2fd8ad458b8b70cefa9b2f8a..188ef0c909d52553570702d4869e4bee05cdaafe 100644 (file)
@@ -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;
index 3991cd453e182ec04ef04dc4f49cf9dab4e772e6..4b4e7a068b6a941dd2b5e91a650c55ccfc14bae3 100644 (file)
@@ -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;
index e91f8387154ffbb2bb61cdaa7ce4352fd4f7da61..cf8f98efae628f5c098679e5ebc4c9723123bd0a 100644 (file)
@@ -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
index 7d280474bd8364ff8ecf0e655eecb141972fb341..38d4866560691c9aabb0dcc329cdd6d759d108f2 100755 (executable)
@@ -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) {
index 3974d45dc33a6789509d01eebc4bda5768e98656..9510783894bf2007e0eca9daf9d323a7653fd31d 100755 (executable)
@@ -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);
index ac914f76d6c133108a4a5a33e0e60df3abf77473..6aaed345edc7d3491e11ef8636348c4abc9b7902 100644 (file)
@@ -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};
             }
index 6abbbb4012fc5432bc0eeb6b2ff40a8870812306..0faae9e7c2e4730c6c25005220f60f4b32557383 100755 (executable)
@@ -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();
index 022e0cac11493bfbf58b67d8f47a922047ce36bb..0b2f42c6d953eca2abe20b9b88bad7cfe87f6190 100644 (file)
@@ -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 " _
index d602171a1fd720fc570a881ec14c59d054c3e337..68e16a022ed88fb0d54cd849e0798a6cd81498d7 100644 (file)
 
       [% 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>
   [% 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 %]">
index 99aed9c22267c4962c9ee142856a5a91f36514e4..257ce4d94b773bc11178530c079ca98c3a55d9e7 100644 (file)
@@ -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 %]">