summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xBugzilla/Bug.pm113
-rw-r--r--Bugzilla/Product.pm70
-rwxr-xr-xprocess_bug.cgi105
-rw-r--r--template/en/default/bug/edit.html.tmpl5
-rw-r--r--template/en/default/bug/process/verify-new-product.html.tmpl2
-rw-r--r--template/en/default/global/user-error.html.tmpl19
6 files changed, 230 insertions, 84 deletions
diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm
index bfd110ad0..e9274466d 100755
--- a/Bugzilla/Bug.pm
+++ b/Bugzilla/Bug.pm
@@ -498,7 +498,29 @@ sub update {
my $old_bug = $self->new($self->id);
my $changes = $self->SUPER::update(@_);
-
+
+ my %old_groups = map {$_->id => $_} @{$old_bug->groups_in};
+ my %new_groups = map {$_->id => $_} @{$self->groups_in};
+ my ($removed_gr, $added_gr) = diff_arrays([keys %old_groups],
+ [keys %new_groups]);
+ if (scalar @$removed_gr || scalar @$added_gr) {
+ if (@$removed_gr) {
+ my $qmarks = join(',', ('?') x @$removed_gr);
+ $dbh->do("DELETE FROM bug_group_map
+ WHERE bug_id = ? AND group_id IN ($qmarks)", undef,
+ $self->id, @$removed_gr);
+ }
+ my $sth_insert = $dbh->prepare(
+ 'INSERT INTO bug_group_map (bug_id, group_id) VALUES (?,?)');
+ foreach my $gid (@$added_gr) {
+ $sth_insert->execute($self->id, $gid);
+ }
+ my @removed_names = map { $old_groups{$_}->name } @$removed_gr;
+ my @added_names = map { $new_groups{$_}->name } @$added_gr;
+ $changes->{'bug_group'} = [join(', ', @removed_names),
+ join(', ', @added_names)];
+ }
+
foreach my $comment (@{$self->{added_comments} || []}) {
my $columns = join(',', keys %$comment);
my @values = values %$comment;
@@ -1599,6 +1621,26 @@ sub set_product {
$self->set_target_milestone($tm_name);
}
+ if ($product_changed) {
+ # Remove groups that aren't valid in the new product. This will also
+ # have the side effect of removing the bug from groups that aren't
+ # active anymore.
+ #
+ # We copy this array because the original array is modified while we're
+ # working, and that confuses "foreach".
+ my @current_groups = @{$self->groups_in};
+ foreach my $group (@current_groups) {
+ if (!grep($group->id == $_->id, @{$product->groups_valid})) {
+ $self->remove_group($group);
+ }
+ }
+
+ # Make sure the bug is in all the mandatory groups for the new product.
+ foreach my $group (@{$product->groups_mandatory_for(Bugzilla->user)}) {
+ $self->add_group($group);
+ }
+ }
+
# XXX This is temporary until all of process_bug uses update();
return $product_changed;
}
@@ -1756,6 +1798,75 @@ sub modify_keywords {
return $any_changes;
}
+sub add_group {
+ my ($self, $group) = @_;
+ # Invalid ids are silently ignored. (We can't tell people whether
+ # or not a group exists.)
+ $group = new Bugzilla::Group($group) unless ref $group;
+ return unless $group;
+
+ # Make sure that bugs in this product can actually be restricted
+ # to this group.
+ grep($group->id == $_->id, @{$self->product_obj->groups_valid})
+ || ThrowUserError('group_invalid_restriction',
+ { product => $self->product, group_id => $group->id });
+
+ # OtherControl people can add groups only during a product change,
+ # and only when the group is not NA for them.
+ if (!Bugzilla->user->in_group($group->name)) {
+ my $controls = $self->product_obj->group_controls->{$group->id};
+ if (!$self->{_old_product_name}
+ || $controls->{othercontrol} == CONTROLMAPNA)
+ {
+ ThrowUserError('group_change_denied',
+ { bug => $self, group_id => $group->id });
+ }
+ }
+
+ my $current_groups = $self->groups_in;
+ if (!grep($group->id == $_->id, @$current_groups)) {
+ push(@$current_groups, $group);
+ }
+}
+
+sub remove_group {
+ my ($self, $group) = @_;
+ $group = new Bugzilla::Group($group) unless ref $group;
+ return unless $group;
+
+ # First, check if this is a valid group for this product.
+ # You can *always* remove a group that is not valid for this product, so
+ # we don't do any other checks if that's the case. (set_product does this.)
+ #
+ # This particularly happens when isbuggroup is no longer 1, and we're
+ # moving a bug to a new product.
+ if (grep($_->id == $group->id, @{$self->product_obj->groups_valid})) {
+ my $controls = $self->product_obj->group_controls->{$group->id};
+
+ # Nobody can ever remove a Mandatory group.
+ if ($controls->{membercontrol} == CONTROLMAPMANDATORY) {
+ ThrowUserError('group_invalid_removal',
+ { product => $self->product, group_id => $group->id,
+ bug => $self });
+ }
+
+ # OtherControl people can remove groups only during a product change,
+ # and only when they are non-Mandatory and non-NA.
+ if (!Bugzilla->user->in_group($group->name)) {
+ if (!$self->{_old_product_name}
+ || $controls->{othercontrol} == CONTROLMAPMANDATORY
+ || $controls->{othercontrol} == CONTROLMAPNA)
+ {
+ ThrowUserError('group_change_denied',
+ { bug => $self, group_id => $group->id });
+ }
+ }
+ }
+
+ my $current_groups = $self->groups_in;
+ @$current_groups = grep { $_->id != $group->id } @$current_groups;
+}
+
#####################################################################
# Instance Accessors
#####################################################################
diff --git a/Bugzilla/Product.pm b/Bugzilla/Product.pm
index 728bd0652..45c489973 100644
--- a/Bugzilla/Product.pm
+++ b/Bugzilla/Product.pm
@@ -21,6 +21,7 @@ package Bugzilla::Product;
use Bugzilla::Version;
use Bugzilla::Milestone;
+use Bugzilla::Constants;
use Bugzilla::Util;
use Bugzilla::Group;
use Bugzilla::Error;
@@ -101,6 +102,39 @@ sub group_controls {
return $self->{group_controls};
}
+sub groups_mandatory_for {
+ my ($self, $user) = @_;
+ my $groups = $user->groups_as_string;
+ my $mandatory = CONTROLMAPMANDATORY;
+ # For membercontrol we don't check group_id IN, because if membercontrol
+ # is Mandatory, the group is Mandatory for everybody, regardless of their
+ # group membership.
+ my $ids = Bugzilla->dbh->selectcol_arrayref(
+ "SELECT group_id FROM group_control_map
+ WHERE product_id = ?
+ AND (membercontrol = $mandatory
+ OR (othercontrol = $mandatory
+ AND group_id NOT IN ($groups)))",
+ undef, $self->id);
+ return Bugzilla::Group->new_from_list($ids);
+}
+
+sub groups_valid {
+ my ($self) = @_;
+ return $self->{groups_valid} if defined $self->{groups_valid};
+
+ # Note that we don't check OtherControl below, because there is no
+ # valid NA/* combination.
+ my $ids = Bugzilla->dbh->selectcol_arrayref(
+ "SELECT DISTINCT group_id
+ FROM group_control_map AS gcm
+ INNER JOIN groups ON gcm.group_id = groups.id
+ WHERE product_id = ? AND isbuggroup = 1
+ AND membercontrol != " . CONTROLMAPNA, undef, $self->id);
+ $self->{groups_valid} = Bugzilla::Group->new_from_list($ids);
+ return $self->{groups_valid};
+}
+
sub versions {
my $self = shift;
my $dbh = Bugzilla->dbh;
@@ -285,6 +319,42 @@ below.
Returns: A hash with group id as key and hash containing
a Bugzilla::Group object and the properties of group
relative to the product.
+
+=item C<groups_mandatory_for>
+
+=over
+
+=item B<Description>
+
+Tells you what groups are mandatory for bugs in this product.
+
+=item B<Params>
+
+C<$user> - The user who you want to check.
+
+=item B<Returns> An arrayref of C<Bugzilla::Group> objects.
+
+=back
+
+=item C<groups_valid>
+
+=over
+
+=item B<Description>
+
+Returns an arrayref of L<Bugzilla::Group> objects, representing groups
+that bugs could validly be restricted to within this product. Used mostly
+by L<Bugzilla::Bug> to assure that you're adding valid groups to a bug.
+
+B<Note>: This doesn't check whether or not the current user can add/remove
+bugs to/from these groups. It just tells you that bugs I<could be in> these
+groups, in this product.
+
+=item B<Params> (none)
+
+=item B<Returns> An arrayref of L<Bugzilla::Group> objects.
+
+=back
=item C<versions()>
diff --git a/process_bug.cgi b/process_bug.cgi
index 91d430aa9..dcea4ffba 100755
--- a/process_bug.cgi
+++ b/process_bug.cgi
@@ -99,7 +99,7 @@ sub send_results {
# Tells us whether or not a field should be changed by process_bug, by
# checking that it's defined and not set to dontchange.
sub should_set {
- # check_defined is used for custom fields, where there's another field
+ # check_defined is used for fields where there's another field
# whose name starts with "defined_" and then the field name--it's used
# to know when we did things like empty a multi-select or deselect
# a checkbox.
@@ -248,6 +248,23 @@ if (should_set('product')) {
other_bugs => \@bug_objects,
});
$product_change ||= $changed;
+
+ # strict_isolation checks mean that we should set the groups
+ # immediately after changing the product.
+ foreach my $group (@{$b->product_obj->groups_valid}) {
+ my $gid = $group->id;
+ if (should_set("bit-$gid", 1)) {
+ # Check ! first to avoid having to check defined below.
+ if (!$cgi->param("bit-$gid")) {
+ $b->remove_group($gid);
+ }
+ # "== 1" is important because mass-change uses -1 to mean
+ # "don't change this restriction"
+ elsif ($cgi->param("bit-$gid") == 1) {
+ $b->add_group($gid);
+ }
+ }
+ }
}
}
@@ -369,16 +386,12 @@ if (defined $cgi->param('id')) {
}
# reporter_accessible and cclist_accessible--these are only set if
- # the user can change them and there are groups on the bug.
- # (If the user can't change the field, the checkboxes don't appear
- # on show_bug, thus it would look like the user was trying to
- # uncheck them, which would then be denied by the set_ functions,
- # throwing a confusing error.)
- if (scalar @{$bug->groups_in}) {
+ # the user can change them and they appear on the page.
+ if (should_set('cclist_accessible', 1)) {
$bug->set_cclist_accessible($cgi->param('cclist_accessible'))
- if $bug->check_can_change_field('cclist_accessible', 0, 1);
+ }
+ if (should_set('reporter_accessible', 1)) {
$bug->set_reporter_accessible($cgi->param('reporter_accessible'))
- if $bug->check_can_change_field('reporter_accessible', 0, 1);
}
# You can only mark/unmark comments as private on single bugs. If
@@ -782,80 +795,6 @@ foreach my $id (@idlist) {
$dbh->do(q{DELETE FROM duplicates WHERE dupe = ?}, undef, $id);
}
- # First of all, get all groups the bug is currently restricted to.
- my $initial_groups =
- $dbh->selectcol_arrayref('SELECT group_id, name
- FROM bug_group_map
- INNER JOIN groups
- ON groups.id = bug_group_map.group_id
- WHERE bug_id = ?', {Columns=>[1,2]}, $id);
- my %original_groups = @$initial_groups;
- my %updated_groups = %original_groups;
-
- # Now let's see which groups have to be added or removed.
- foreach my $gid (keys %{$new_product->group_controls}) {
- my $group = $new_product->group_controls->{$gid};
- # Leave inactive groups alone.
- next unless $group->{group}->is_active;
-
- # Only members of a group can add/remove the bug to/from it,
- # unless the bug is being moved to another product in which case
- # non-members can also edit group restrictions.
- if ($group->{membercontrol} == CONTROLMAPMANDATORY
- || ($product_change && $group->{othercontrol} == CONTROLMAPMANDATORY
- && !$user->in_group_id($gid)))
- {
- $updated_groups{$gid} = $group->{group}->name;
- }
- elsif ($group->{membercontrol} == CONTROLMAPNA
- || ($product_change && $group->{othercontrol} == CONTROLMAPNA
- && !$user->in_group_id($gid)))
- {
- delete $updated_groups{$gid};
- }
- # When editing several bugs at once, only consider groups which
- # have been displayed.
- elsif (($user->in_group_id($gid) || $product_change)
- && ((defined $cgi->param('id') && Bugzilla->usage_mode != USAGE_MODE_EMAIL)
- || defined $cgi->param("bit-$gid")))
- {
- if (!$cgi->param("bit-$gid")) {
- delete $updated_groups{$gid};
- }
- # Note that == 1 is important, because == -1 means "ignore this group".
- elsif ($cgi->param("bit-$gid") == 1) {
- $updated_groups{$gid} = $group->{group}->name;
- }
- }
- }
- # We also have to remove groups which are not legal in the new product.
- foreach my $gid (keys %updated_groups) {
- delete $updated_groups{$gid}
- unless exists $new_product->group_controls->{$gid};
- }
- my ($removed, $added) = diff_arrays([keys %original_groups], [keys %updated_groups]);
-
- # We can now update the DB.
- if (scalar(@$removed)) {
- $dbh->do('DELETE FROM bug_group_map WHERE bug_id = ?
- AND group_id IN (' . join(', ', @$removed) . ')',
- undef, $id);
- }
- if (scalar(@$added)) {
- my $sth = $dbh->prepare('INSERT INTO bug_group_map
- (bug_id, group_id) VALUES (?, ?)');
- $sth->execute($id, $_) foreach @$added;
- }
-
- # Add the changes to the bug_activity table.
- if (scalar(@$removed) || scalar(@$added)) {
- my @removed_names = map { $original_groups{$_} } @$removed;
- my @added_names = map { $updated_groups{$_} } @$added;
- LogActivityEntry($id, 'bug_group', join(',', @removed_names),
- join(',', @added_names), $whoid, $timestamp);
- $bug_changed = 1;
- }
-
my ($cc_removed) = $bug_objects{$id}->update_cc($timestamp);
$cc_removed = [map {$_->login} @$cc_removed];
diff --git a/template/en/default/bug/edit.html.tmpl b/template/en/default/bug/edit.html.tmpl
index 1f20cdb52..c37f93838 100644
--- a/template/en/default/bug/edit.html.tmpl
+++ b/template/en/default/bug/edit.html.tmpl
@@ -498,6 +498,9 @@
[% END %]
&nbsp;&nbsp;&nbsp;&nbsp;
+ [% IF group.ingroup %]
+ <input type="hidden" name="defined_bit-[% group.bit %]" value="1">
+ [% END %]
<input type="checkbox" value="1"
name="bit-[% group.bit %]" id="bit-[% group.bit %]"
[% " checked=\"checked\"" IF group.ison %]
@@ -530,11 +533,13 @@
</p>
<p>
+ <input type="hidden" name="defined_reporter_accessible" value="1">
<input type="checkbox" value="1"
name="reporter_accessible" id="reporter_accessible"
[% " checked" IF bug.reporter_accessible %]
[% " disabled=\"disabled\"" UNLESS bug.check_can_change_field("reporter_accessible", 0, 1) %]>
<label for="reporter_accessible">Reporter</label>
+ <input type="hidden" name="defined_cclist_accessible" value="1">
<input type="checkbox" value="1"
name="cclist_accessible" id="cclist_accessible"
[% " checked" IF bug.cclist_accessible %]
diff --git a/template/en/default/bug/process/verify-new-product.html.tmpl b/template/en/default/bug/process/verify-new-product.html.tmpl
index ee87ef818..d1ce9652b 100644
--- a/template/en/default/bug/process/verify-new-product.html.tmpl
+++ b/template/en/default/bug/process/verify-new-product.html.tmpl
@@ -145,6 +145,8 @@
<p>These groups are optional. You can decide to restrict [% terms.bugs %] to
one or more of the following groups:<br>
[% FOREACH group = optional_groups %]
+ <input type="hidden" name="defined_bit-[% group.group.id FILTER html %]"
+ value="1">
<input type="checkbox" id="bit-[% group.group.id FILTER html %]"
name="bit-[% group.group.id FILTER html %]"
[% ((group.membercontrol == constants.CONTROLMAPDEFAULT && user.in_group(group.group.name))
diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl
index 2848ab5a4..46ed901a2 100644
--- a/template/en/default/global/user-error.html.tmpl
+++ b/template/en/default/global/user-error.html.tmpl
@@ -600,6 +600,12 @@
in the database which refer to it. All references to this group must
be removed before you can remove it.
+ [% ELSIF error == "group_change_denied" %]
+ [% title = "Cannot Add/Remove That Group" %]
+ You tried to add or remove group id [% group_id FILTER html %]
+ from [% terms.bug %] [%+ bug.id FILTER html %], but you do not
+ have permissions to do so.
+
[% ELSIF error == "group_exists" %]
[% title = "The group already exists" %]
The group [% name FILTER html %] already exists.
@@ -618,6 +624,19 @@
In order to delete this group, you first have to change the
[%+ param FILTER html %] to make [% attr FILTER html %] point to another group.
+
+ [% ELSIF error == "group_invalid_removal" %]
+ You tried to remove [% terms.bug %] [%+ bug.id FILTER html %]
+ from group id [% group_id FILTER html %], but [% terms.bugs %] in the
+ '[% product.name FILTER html %]' product can not be removed from that
+ group.
+
+ [% ELSIF error == "group_invalid_restriction" %]
+ You tried to restrict [% terms.bug %] [%+ bug.id FILTER html %] to
+ to group id [% group_id FILTER html %], but [% terms.bugs %] in the
+ '[% product.name FILTER html %]' product can not be restricted to
+ that group.
+
[% ELSIF error == "group_not_specified" %]
[% title = "Group not specified" %]
No group was specified.