summaryrefslogtreecommitdiffstats
path: root/Bugzilla/Bug.pm
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/Bug.pm')
-rwxr-xr-xBugzilla/Bug.pm236
1 files changed, 163 insertions, 73 deletions
diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm
index 7c0cc191f..466ceb988 100755
--- a/Bugzilla/Bug.pm
+++ b/Bugzilla/Bug.pm
@@ -74,6 +74,7 @@ sub DB_COLUMNS {
my @custom_names = map {$_->name} @custom;
return qw(
alias
+ assigned_to
bug_file_loc
bug_id
bug_severity
@@ -86,6 +87,7 @@ sub DB_COLUMNS {
op_sys
priority
product_id
+ qa_contact
remaining_time
rep_platform
reporter_accessible
@@ -95,9 +97,7 @@ sub DB_COLUMNS {
target_milestone
version
),
- 'assigned_to AS assigned_to_id',
'reporter AS reporter_id',
- 'qa_contact AS qa_contact_id',
$dbh->sql_date_format('creation_ts', '%Y.%m.%d %H:%i') . ' AS creation_ts',
$dbh->sql_date_format('deadline', '%Y-%m-%d') . ' AS deadline',
@custom_names;
@@ -158,8 +158,10 @@ sub VALIDATORS {
};
use constant UPDATE_VALIDATORS => {
+ assigned_to => \&_check_assigned_to,
bug_status => \&_check_bug_status,
cclist_accessible => \&Bugzilla::Object::check_boolean,
+ qa_contact => \&_check_qa_contact,
reporter_accessible => \&Bugzilla::Object::check_boolean,
resolution => \&_check_resolution,
target_milestone => \&_check_target_milestone,
@@ -172,6 +174,7 @@ sub UPDATE_COLUMNS {
my @custom_names = map {$_->name} @custom;
my @columns = qw(
alias
+ assigned_to
cclist_accessible
component_id
deadline
@@ -183,6 +186,7 @@ sub UPDATE_COLUMNS {
op_sys
priority
product_id
+ qa_contact
remaining_time
rep_platform
reporter_accessible
@@ -452,9 +456,9 @@ sub run_create_validators {
delete $params->{component};
$params->{assigned_to} =
- $class->_check_assigned_to($component, $params->{assigned_to});
+ $class->_check_assigned_to($params->{assigned_to}, $component);
$params->{qa_contact} =
- $class->_check_qa_contact($component, $params->{qa_contact});
+ $class->_check_qa_contact($params->{qa_contact}, $component);
$params->{cc} = $class->_check_cc($component, $params->{cc});
# Callers cannot set Reporter, currently.
@@ -467,8 +471,8 @@ sub run_create_validators {
$params->{remaining_time} = $params->{estimated_time};
}
- $class->_check_strict_isolation($product, $params->{cc},
- $params->{assigned_to}, $params->{qa_contact});
+ $class->_check_strict_isolation($params->{cc}, $params->{assigned_to},
+ $params->{qa_contact}, $product);
($params->{dependson}, $params->{blocked}) =
$class->_check_dependencies($params->{dependson}, $params->{blocked},
@@ -545,6 +549,10 @@ sub update {
$from = $self->{"_old_${field}_name"};
$to = $self->$field;
}
+ if (grep ($_ eq $field, qw(qa_contact assigned_to))) {
+ $from = $old_bug->$field->login if $from;
+ $to = $self->$field->login if $to;
+ }
LogActivityEntry($self->id, $field, $from, $to, Bugzilla->user->id,
$delta_ts);
}
@@ -814,16 +822,28 @@ sub _check_alias {
}
sub _check_assigned_to {
- my ($invocant, $component, $name) = @_;
+ my ($invocant, $assignee, $component) = @_;
my $user = Bugzilla->user;
- $name = trim($name);
# Default assignee is the component owner.
my $id;
- if (!$user->in_group('editbugs', $component->product_id) || !$name) {
+ # If this is a new bug, you can only set the assignee if you have editbugs.
+ # If you didn't specify the assignee, we use the default assignee.
+ if (!ref $invocant
+ && (!$user->in_group('editbugs', $component->product_id) || !$assignee))
+ {
$id = $component->default_assignee->id;
} else {
- $id = login_to_id($name, THROW_ERROR);
+ if (!ref $assignee) {
+ $assignee = trim($assignee);
+ # When updating a bug, assigned_to can't be empty.
+ ThrowUserError("reassign_to_empty") if ref $invocant && !$assignee;
+ $assignee = Bugzilla::User->check($assignee);
+ }
+ $id = $assignee->id;
+ # create() checks this another way, so we don't have to run this
+ # check during create().
+ $invocant->_check_strict_isolation_for_user($assignee) if ref $invocant;
}
return $id;
}
@@ -1134,6 +1154,38 @@ sub _check_priority {
return $priority;
}
+sub _check_qa_contact {
+ my ($invocant, $qa_contact, $component) = @_;
+ $qa_contact = trim($qa_contact) if !ref $qa_contact;
+
+ my $id;
+ if (!ref $invocant) {
+ # Bugs get no QA Contact on creation if useqacontact is off.
+ return undef if !Bugzilla->params->{useqacontact};
+ # Set the default QA Contact if one isn't specified or if the
+ # user doesn't have editbugs.
+ if (!Bugzilla->user->in_group('editbugs', $component->product_id)
+ || !$qa_contact)
+ {
+ $id = $component->default_qa_contact->id;
+ }
+ }
+
+ # If a QA Contact was specified or if we're updating, check
+ # the QA Contact for validity.
+ if (!defined $id && $qa_contact) {
+ $qa_contact = Bugzilla::User->check($qa_contact) if !ref $qa_contact;
+ $id = $qa_contact->id;
+ # create() checks this another way, so we don't have to run this
+ # check during create().
+ $invocant->_check_strict_isolation_for_user($qa_contact)
+ if ref $invocant;
+ }
+
+ # "0" always means "undef", for QA Contact.
+ return $id || undef;
+}
+
sub _check_remaining_time {
return $_[0]->_check_time($_[1], 'remaining_time');
}
@@ -1167,33 +1219,69 @@ sub _check_status_whiteboard { return defined $_[1] ? $_[1] : ''; }
# Unlike other checkers, this one doesn't return anything.
sub _check_strict_isolation {
- my ($invocant, $product, $cc_ids, $assignee_id, $qa_contact_id) = @_;
-
+ my ($invocant, $ccs, $assignee, $qa_contact, $product) = @_;
return unless Bugzilla->params->{'strict_isolation'};
- my @related_users = @$cc_ids;
- push(@related_users, $assignee_id);
+ if (ref $invocant) {
+ my $original = $invocant->new($invocant->id);
+
+ # We only check people if they've been added. This way, if
+ # strict_isolation is turned on when there are invalid users
+ # on bugs, people can still add comments and so on.
+ my @old_cc = map { $_->id } @{$original->cc_users};
+ my @new_cc = map { $_->id } @{$invocant->cc_users};
+ my ($removed, $added) = diff_arrays(\@old_cc, \@new_cc);
+ $ccs = $added;
+ $assignee = $invocant->assigned_to
+ if $invocant->assigned_to->id != $original->assigned_to->id;
+ $qa_contact = $invocant->qa_contact
+ if $invocant->qa_contact->id != $original->qa_contact->id;
+ $product = $invocant->product;
+ }
+
+ my @related_users = @$ccs;
+ push(@related_users, $assignee) if $assignee;
- if (Bugzilla->params->{'useqacontact'} && $qa_contact_id) {
- push(@related_users, $qa_contact_id);
+ if (Bugzilla->params->{'useqacontact'} && $qa_contact) {
+ push(@related_users, $qa_contact);
}
+ @related_users = @{Bugzilla::User->new_from_list(\@related_users)}
+ if !ref $invocant;
+
# For each unique user in @related_users...(assignee and qa_contact
# could be duplicates of users in the CC list)
- my %unique_users = map {$_ => 1} @related_users;
+ my %unique_users = map {$_->id => $_} @related_users;
my @blocked_users;
- foreach my $pid (keys %unique_users) {
- my $related_user = Bugzilla::User->new($pid);
+ foreach my $id (keys %unique_users) {
+ my $related_user = $unique_users{$id};
if (!$related_user->can_edit_product($product->id) ||
!$related_user->can_see_product($product->id)) {
push (@blocked_users, $related_user->login);
}
}
if (scalar(@blocked_users)) {
- ThrowUserError("invalid_user_group",
- {'users' => \@blocked_users,
- 'new' => 1,
- 'product' => $product->name});
+ my %vars = ( users => \@blocked_users,
+ product => $product->name );
+ if (ref $invocant) {
+ $vars{'bug_id'} = $invocant->id;
+ }
+ else {
+ $vars{'new'} = 1;
+ }
+ ThrowUserError("invalid_user_group", \%vars);
+ }
+}
+
+# This is used by various set_ checkers, to make their code simpler.
+sub _check_strict_isolation_for_user {
+ my ($self, $user) = @_;
+ return unless Bugzilla->params->{"strict_isolation"};
+ if (!$user->can_edit_product($self->{product_id})) {
+ ThrowUserError('invalid_user_group',
+ { users => $user->login,
+ product => $self->product,
+ bug_id => $self->id });
}
}
@@ -1223,25 +1311,6 @@ sub _check_time {
return $time;
}
-sub _check_qa_contact {
- my ($invocant, $component, $name) = @_;
- my $user = Bugzilla->user;
-
- return undef unless Bugzilla->params->{'useqacontact'};
-
- $name = trim($name);
-
- my $id;
- if (!$user->in_group('editbugs', $component->product_id) || !$name) {
- # We want to insert NULL into the database if we get a 0.
- $id = $component->default_qa_contact->id || undef;
- } else {
- $id = login_to_id($name, THROW_ERROR);
- }
-
- return $id;
-}
-
sub _check_version {
my ($invocant, $version, $product) = @_;
$version = trim($version);
@@ -1331,13 +1400,21 @@ sub fields {
# To run check_can_change_field.
sub _set_global_validator {
my ($self, $value, $field) = @_;
- my $current_value = $self->$field;
+ my $current = $self->$field;
my $privs;
- $self->check_can_change_field($field, $current_value, $value, \$privs)
- || ThrowUserError('illegal_change', { field => $field,
- oldvalue => $current_value,
- newvalue => $value,
- privs => $privs });
+ $current = $current->id if ref $current && $current->isa('Bugzilla::Object');
+ $value = $value->id if ref $value && $value->isa('Bugzilla::Object');
+ my $can = $self->check_can_change_field($field, $current, $value, \$privs);
+ if (!$can) {
+ if ($field eq 'assigned_to' || $field eq 'qa_contact') {
+ $value = user_id_to_login($value);
+ $current = user_id_to_login($current);
+ }
+ ThrowUserError('illegal_change', { field => $field,
+ oldvalue => $current,
+ newvalue => $value,
+ privs => $privs });
+ }
}
@@ -1346,6 +1423,16 @@ sub _set_global_validator {
#################
sub set_alias { $_[0]->set('alias', $_[1]); }
+sub set_assigned_to {
+ my ($self, $value) = @_;
+ $self->set('assigned_to', $value);
+ delete $self->{'assigned_to_obj'};
+}
+sub reset_assigned_to {
+ my $self = shift;
+ my $comp = $self->component_obj;
+ $self->set_assigned_to($comp->default_assignee);
+}
sub set_cclist_accessible { $_[0]->set('cclist_accessible', $_[1]); }
sub set_comment_is_private {
my ($self, $comment_id, $isprivate) = @_;
@@ -1505,6 +1592,16 @@ sub set_product {
return $product_changed;
}
+sub set_qa_contact {
+ my ($self, $value) = @_;
+ $self->set('qa_contact', $value);
+ delete $self->{'qa_contact_obj'};
+}
+sub reset_qa_contact {
+ my $self = shift;
+ my $comp = $self->component_obj;
+ $self->set_qa_contact($comp->default_qa_contact);
+}
sub set_remaining_time { $_[0]->set('remaining_time', $_[1]); }
# Used only when closing a bug or moving between closed states.
sub _zero_remaining_time { $_[0]->{'remaining_time'} = 0; }
@@ -1512,9 +1609,9 @@ sub set_reporter_accessible { $_[0]->set('reporter_accessible', $_[1]); }
sub set_resolution { $_[0]->set('resolution', $_[1]); }
sub clear_resolution { $_[0]->{'resolution'} = '' }
sub set_severity { $_[0]->set('bug_severity', $_[1]); }
-sub set_status {
+sub set_status {
my ($self, $status) = @_;
- $self->set('bug_status', $status);
+ $self->set('bug_status', $status);
# Check for the everconfirmed transition
$self->_set_everconfirmed(1) if (is_open_state($status) && $status ne 'UNCONFIRMED');
}
@@ -1537,14 +1634,7 @@ sub add_cc {
return if !$user_or_name;
my $user = ref $user_or_name ? $user_or_name
: Bugzilla::User->check($user_or_name);
-
- if (Bugzilla->params->{strict_isolation}
- && !$user->can_edit_product($self->product_obj->id))
- {
- ThrowUserError('invalid_user_group', { users => $user->login,
- bug_id => $self->id });
- }
-
+ $self->_check_strict_isolation_for_user($user);
my $cc_users = $self->cc_users;
push(@$cc_users, $user) if !grep($_->id == $user->id, @$cc_users);
}
@@ -1729,10 +1819,10 @@ sub attachments {
sub assigned_to {
my ($self) = @_;
- return $self->{'assigned_to'} if exists $self->{'assigned_to'};
- $self->{'assigned_to_id'} = 0 if $self->{'error'};
- $self->{'assigned_to'} = new Bugzilla::User($self->{'assigned_to_id'});
- return $self->{'assigned_to'};
+ return $self->{'assigned_to_obj'} if exists $self->{'assigned_to_obj'};
+ $self->{'assigned_to'} = 0 if $self->{'error'};
+ $self->{'assigned_to_obj'} ||= new Bugzilla::User($self->{'assigned_to'});
+ return $self->{'assigned_to_obj'};
}
sub blocked {
@@ -1914,18 +2004,18 @@ sub product_obj {
sub qa_contact {
my ($self) = @_;
- return $self->{'qa_contact'} if exists $self->{'qa_contact'};
+ return $self->{'qa_contact_obj'} if exists $self->{'qa_contact_obj'};
return undef if $self->{'error'};
- if (Bugzilla->params->{'useqacontact'} && $self->{'qa_contact_id'}) {
- $self->{'qa_contact'} = new Bugzilla::User($self->{'qa_contact_id'});
+ if (Bugzilla->params->{'useqacontact'} && $self->{'qa_contact'}) {
+ $self->{'qa_contact_obj'} = new Bugzilla::User($self->{'qa_contact'});
} else {
# XXX - This is somewhat inconsistent with the assignee/reporter
# methods, which will return an empty User if they get a 0.
# However, we're keeping it this way now, for backwards-compatibility.
- $self->{'qa_contact'} = undef;
+ $self->{'qa_contact_obj'} = undef;
}
- return $self->{'qa_contact'};
+ return $self->{'qa_contact_obj'};
}
sub reporter {
@@ -2065,10 +2155,10 @@ sub user {
my $unknown_privileges = $user->in_group('editbugs', $prod_id);
my $canedit = $unknown_privileges
- || $user->id == $self->{assigned_to_id}
+ || $user->id == $self->{'assigned_to'}
|| (Bugzilla->params->{'useqacontact'}
- && $self->{'qa_contact_id'}
- && $user->id == $self->{qa_contact_id});
+ && $self->{'qa_contact'}
+ && $user->id == $self->{'qa_contact'});
my $canconfirm = $unknown_privileges
|| $user->in_group('canconfirm', $prod_id);
my $isreporter = $user->id
@@ -2991,14 +3081,14 @@ sub check_can_change_field {
# Make sure that a valid bug ID has been given.
if (!$self->{'error'}) {
# Allow the assignee to change anything else.
- if ($self->{'assigned_to_id'} == $user->id) {
+ if ($self->{'assigned_to'} == $user->id) {
return 1;
}
# Allow the QA contact to change anything else.
if (Bugzilla->params->{'useqacontact'}
- && $self->{'qa_contact_id'}
- && ($self->{'qa_contact_id'} == $user->id))
+ && $self->{'qa_contact'}
+ && ($self->{'qa_contact'} == $user->id))
{
return 1;
}