summaryrefslogtreecommitdiffstats
path: root/Bugzilla
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla')
-rwxr-xr-xBugzilla/Bug.pm113
-rw-r--r--Bugzilla/Product.pm70
2 files changed, 182 insertions, 1 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()>