summaryrefslogtreecommitdiffstats
path: root/Bugzilla/Flag.pm
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/Flag.pm')
-rw-r--r--Bugzilla/Flag.pm1531
1 files changed, 799 insertions, 732 deletions
diff --git a/Bugzilla/Flag.pm b/Bugzilla/Flag.pm
index 625794974..3ed055b3d 100644
--- a/Bugzilla/Flag.pm
+++ b/Bugzilla/Flag.pm
@@ -58,8 +58,9 @@ use base qw(Bugzilla::Object Exporter);
#### Initialization ####
###############################
-use constant DB_TABLE => 'flags';
+use constant DB_TABLE => 'flags';
use constant LIST_ORDER => 'id';
+
# Flags are tracked in bugs_activity.
use constant AUDIT_CREATES => 0;
use constant AUDIT_UPDATES => 0;
@@ -70,35 +71,32 @@ use constant SKIP_REQUESTEE_ON_ERROR => 1;
our $disable_flagmail = 0;
sub DB_COLUMNS {
- my $dbh = Bugzilla->dbh;
- return qw(
- id
- type_id
- bug_id
- attach_id
- requestee_id
- setter_id
- status),
- $dbh->sql_date_format('creation_date', '%Y.%m.%d %H:%i:%s') .
- ' AS creation_date',
- $dbh->sql_date_format('modification_date', '%Y.%m.%d %H:%i:%s') .
- ' AS modification_date';
+ my $dbh = Bugzilla->dbh;
+ return qw(
+ id
+ type_id
+ bug_id
+ attach_id
+ requestee_id
+ setter_id
+ status),
+ $dbh->sql_date_format('creation_date', '%Y.%m.%d %H:%i:%s')
+ . ' AS creation_date',
+ $dbh->sql_date_format('modification_date', '%Y.%m.%d %H:%i:%s')
+ . ' AS modification_date';
}
use constant UPDATE_COLUMNS => qw(
- requestee_id
- setter_id
- status
- type_id
+ requestee_id
+ setter_id
+ status
+ type_id
);
-use constant VALIDATORS => {
-};
+use constant VALIDATORS => {};
-use constant UPDATE_VALIDATORS => {
- setter => \&_check_setter,
- status => \&_check_status,
-};
+use constant UPDATE_VALIDATORS =>
+ {setter => \&_check_setter, status => \&_check_status,};
###############################
#### Accessors ######
@@ -140,15 +138,15 @@ Returns the timestamp when the flag was last modified.
=cut
-sub id { return $_[0]->{'id'}; }
-sub name { return $_[0]->type->name; }
-sub type_id { return $_[0]->{'type_id'}; }
-sub bug_id { return $_[0]->{'bug_id'}; }
-sub attach_id { return $_[0]->{'attach_id'}; }
-sub status { return $_[0]->{'status'}; }
-sub setter_id { return $_[0]->{'setter_id'}; }
-sub requestee_id { return $_[0]->{'requestee_id'}; }
-sub creation_date { return $_[0]->{'creation_date'}; }
+sub id { return $_[0]->{'id'}; }
+sub name { return $_[0]->type->name; }
+sub type_id { return $_[0]->{'type_id'}; }
+sub bug_id { return $_[0]->{'bug_id'}; }
+sub attach_id { return $_[0]->{'attach_id'}; }
+sub status { return $_[0]->{'status'}; }
+sub setter_id { return $_[0]->{'setter_id'}; }
+sub requestee_id { return $_[0]->{'requestee_id'}; }
+sub creation_date { return $_[0]->{'creation_date'}; }
sub modification_date { return $_[0]->{'modification_date'}; }
###############################
@@ -182,44 +180,43 @@ is an attachment flag, else undefined.
=cut
sub type {
- my $self = shift;
+ my $self = shift;
- return $self->{'type'}
- ||= new Bugzilla::FlagType($self->{'type_id'}, cache => 1 );
+ return $self->{'type'}
+ ||= new Bugzilla::FlagType($self->{'type_id'}, cache => 1);
}
sub setter {
- my $self = shift;
+ my $self = shift;
- return $self->{'setter' }
- ||= new Bugzilla::User({ id => $self->{'setter_id'}, cache => 1 });
+ return $self->{'setter'}
+ ||= new Bugzilla::User({id => $self->{'setter_id'}, cache => 1});
}
sub requestee {
- my $self = shift;
+ my $self = shift;
- if (!defined $self->{'requestee'} && $self->{'requestee_id'}) {
- $self->{'requestee'}
- = new Bugzilla::User({ id => $self->{'requestee_id'}, cache => 1 });
- }
- return $self->{'requestee'};
+ if (!defined $self->{'requestee'} && $self->{'requestee_id'}) {
+ $self->{'requestee'}
+ = new Bugzilla::User({id => $self->{'requestee_id'}, cache => 1});
+ }
+ return $self->{'requestee'};
}
sub attachment {
- my $self = shift;
- return undef unless $self->attach_id;
+ my $self = shift;
+ return undef unless $self->attach_id;
- require Bugzilla::Attachment;
- return $self->{'attachment'}
- ||= new Bugzilla::Attachment({ id => $self->attach_id, cache => 1 });
+ require Bugzilla::Attachment;
+ return $self->{'attachment'}
+ ||= new Bugzilla::Attachment({id => $self->attach_id, cache => 1});
}
sub bug {
- my $self = shift;
+ my $self = shift;
- require Bugzilla::Bug;
- return $self->{'bug'}
- ||= new Bugzilla::Bug({ id => $self->bug_id, cache => 1 });
+ require Bugzilla::Bug;
+ return $self->{'bug'} ||= new Bugzilla::Bug({id => $self->bug_id, cache => 1});
}
################################
@@ -241,26 +238,27 @@ and returns an array of matching records.
=cut
sub match {
- my $class = shift;
- my ($criteria) = @_;
-
- # If the caller specified only bug or attachment flags,
- # limit the query to those kinds of flags.
- if (my $type = delete $criteria->{'target_type'}) {
- if ($type eq 'bug') {
- $criteria->{'attach_id'} = IS_NULL;
- }
- elsif (!defined $criteria->{'attach_id'}) {
- $criteria->{'attach_id'} = NOT_NULL;
- }
+ my $class = shift;
+ my ($criteria) = @_;
+
+ # If the caller specified only bug or attachment flags,
+ # limit the query to those kinds of flags.
+ if (my $type = delete $criteria->{'target_type'}) {
+ if ($type eq 'bug') {
+ $criteria->{'attach_id'} = IS_NULL;
}
- # Flag->snapshot() calls Flag->match() with bug_id and attach_id
- # as hash keys, even if attach_id is undefined.
- if (exists $criteria->{'attach_id'} && !defined $criteria->{'attach_id'}) {
- $criteria->{'attach_id'} = IS_NULL;
+ elsif (!defined $criteria->{'attach_id'}) {
+ $criteria->{'attach_id'} = NOT_NULL;
}
+ }
+
+ # Flag->snapshot() calls Flag->match() with bug_id and attach_id
+ # as hash keys, even if attach_id is undefined.
+ if (exists $criteria->{'attach_id'} && !defined $criteria->{'attach_id'}) {
+ $criteria->{'attach_id'} = IS_NULL;
+ }
- return $class->SUPER::match(@_);
+ return $class->SUPER::match(@_);
}
=pod
@@ -278,8 +276,8 @@ and returns an array of matching records.
=cut
sub count {
- my $class = shift;
- return scalar @{$class->match(@_)};
+ my $class = shift;
+ return scalar @{$class->match(@_)};
}
######################################################################
@@ -287,145 +285,157 @@ sub count {
######################################################################
sub set_flag {
- my ($class, $obj, $params) = @_;
-
- my ($bug, $attachment, $obj_flag, $requestee_changed);
- if (blessed($obj) && $obj->isa('Bugzilla::Attachment')) {
- $attachment = $obj;
- $bug = $attachment->bug;
- }
- elsif (blessed($obj) && $obj->isa('Bugzilla::Bug')) {
- $bug = $obj;
- }
- else {
- ThrowCodeError('flag_unexpected_object', { 'caller' => ref $obj });
- }
-
- # Make sure the user can change flags
- my $privs;
- $bug->check_can_change_field('flagtypes.name', 0, 1, \$privs)
- || ThrowUserError('illegal_change',
- { field => 'flagtypes.name', privs => $privs });
-
- # Update (or delete) an existing flag.
- if ($params->{id}) {
- my $flag = $class->check({ id => $params->{id} });
-
- # Security check: make sure the flag belongs to the bug/attachment.
- # We don't check that the user editing the flag can see
- # the bug/attachment. That's the job of the caller.
- ($attachment && $flag->attach_id && $attachment->id == $flag->attach_id)
- || (!$attachment && !$flag->attach_id && $bug->id == $flag->bug_id)
- || ThrowCodeError('invalid_flag_association',
- { bug_id => $bug->id,
- attach_id => $attachment ? $attachment->id : undef });
-
- # Extract the current flag object from the object.
- my ($obj_flagtype) = grep { $_->id == $flag->type_id } @{$obj->flag_types};
- # If no flagtype can be found for this flag, this means the bug is being
- # moved into a product/component where the flag is no longer valid.
- # So either we can attach the flag to another flagtype having the same
- # name, or we remove the flag.
- if (!$obj_flagtype) {
- my $success = $flag->retarget($obj);
- return unless $success;
-
- ($obj_flagtype) = grep { $_->id == $flag->type_id } @{$obj->flag_types};
- push(@{$obj_flagtype->{flags}}, $flag);
- }
- ($obj_flag) = grep { $_->id == $flag->id } @{$obj_flagtype->{flags}};
- # If the flag has the correct type but cannot be found above, this means
- # the flag is going to be removed (e.g. because this is a pending request
- # and the attachment is being marked as obsolete).
- return unless $obj_flag;
-
- ($obj_flag, $requestee_changed) =
- $class->_validate($obj_flag, $obj_flagtype, $params, $bug, $attachment);
+ my ($class, $obj, $params) = @_;
+
+ my ($bug, $attachment, $obj_flag, $requestee_changed);
+ if (blessed($obj) && $obj->isa('Bugzilla::Attachment')) {
+ $attachment = $obj;
+ $bug = $attachment->bug;
+ }
+ elsif (blessed($obj) && $obj->isa('Bugzilla::Bug')) {
+ $bug = $obj;
+ }
+ else {
+ ThrowCodeError('flag_unexpected_object', {'caller' => ref $obj});
+ }
+
+ # Make sure the user can change flags
+ my $privs;
+ $bug->check_can_change_field('flagtypes.name', 0, 1, \$privs)
+ || ThrowUserError('illegal_change',
+ {field => 'flagtypes.name', privs => $privs});
+
+ # Update (or delete) an existing flag.
+ if ($params->{id}) {
+ my $flag = $class->check({id => $params->{id}});
+
+ # Security check: make sure the flag belongs to the bug/attachment.
+ # We don't check that the user editing the flag can see
+ # the bug/attachment. That's the job of the caller.
+ ($attachment && $flag->attach_id && $attachment->id == $flag->attach_id)
+ || (!$attachment && !$flag->attach_id && $bug->id == $flag->bug_id)
+ || ThrowCodeError('invalid_flag_association',
+ {bug_id => $bug->id, attach_id => $attachment ? $attachment->id : undef});
+
+ # Extract the current flag object from the object.
+ my ($obj_flagtype) = grep { $_->id == $flag->type_id } @{$obj->flag_types};
+
+ # If no flagtype can be found for this flag, this means the bug is being
+ # moved into a product/component where the flag is no longer valid.
+ # So either we can attach the flag to another flagtype having the same
+ # name, or we remove the flag.
+ if (!$obj_flagtype) {
+ my $success = $flag->retarget($obj);
+ return unless $success;
+
+ ($obj_flagtype) = grep { $_->id == $flag->type_id } @{$obj->flag_types};
+ push(@{$obj_flagtype->{flags}}, $flag);
}
- # Create a new flag.
- elsif ($params->{type_id}) {
- # Don't bother validating types the user didn't touch.
- return if $params->{status} eq 'X';
-
- my $flagtype = Bugzilla::FlagType->check({ id => $params->{type_id} });
- # Security check: make sure the flag type belongs to the bug/attachment.
- ($attachment && $flagtype->target_type eq 'attachment'
- && scalar(grep { $_->id == $flagtype->id } @{$attachment->flag_types}))
- || (!$attachment && $flagtype->target_type eq 'bug'
- && scalar(grep { $_->id == $flagtype->id } @{$bug->flag_types}))
- || ThrowCodeError('invalid_flag_association',
- { bug_id => $bug->id,
- attach_id => $attachment ? $attachment->id : undef });
-
- # Make sure the flag type is active.
- $flagtype->is_active
- || ThrowCodeError('flag_type_inactive', { type => $flagtype->name });
-
- # Extract the current flagtype object from the object.
- my ($obj_flagtype) = grep { $_->id == $flagtype->id } @{$obj->flag_types};
-
- # We cannot create a new flag if there is already one and this
- # flag type is not multiplicable.
- if (!$flagtype->is_multiplicable) {
- if (scalar @{$obj_flagtype->{flags}}) {
- ThrowUserError('flag_type_not_multiplicable', { type => $flagtype });
- }
- }
-
- ($obj_flag, $requestee_changed) =
- $class->_validate(undef, $obj_flagtype, $params, $bug, $attachment);
- }
- else {
- ThrowCodeError('param_required', { function => $class . '->set_flag',
- param => 'id/type_id' });
+ ($obj_flag) = grep { $_->id == $flag->id } @{$obj_flagtype->{flags}};
+
+ # If the flag has the correct type but cannot be found above, this means
+ # the flag is going to be removed (e.g. because this is a pending request
+ # and the attachment is being marked as obsolete).
+ return unless $obj_flag;
+
+ ($obj_flag, $requestee_changed)
+ = $class->_validate($obj_flag, $obj_flagtype, $params, $bug, $attachment);
+ }
+
+ # Create a new flag.
+ elsif ($params->{type_id}) {
+
+ # Don't bother validating types the user didn't touch.
+ return if $params->{status} eq 'X';
+
+ my $flagtype = Bugzilla::FlagType->check({id => $params->{type_id}});
+
+ # Security check: make sure the flag type belongs to the bug/attachment.
+ ( $attachment
+ && $flagtype->target_type eq 'attachment'
+ && scalar(grep { $_->id == $flagtype->id } @{$attachment->flag_types}))
+ || (!$attachment
+ && $flagtype->target_type eq 'bug'
+ && scalar(grep { $_->id == $flagtype->id } @{$bug->flag_types}))
+ || ThrowCodeError('invalid_flag_association',
+ {bug_id => $bug->id, attach_id => $attachment ? $attachment->id : undef});
+
+ # Make sure the flag type is active.
+ $flagtype->is_active
+ || ThrowCodeError('flag_type_inactive', {type => $flagtype->name});
+
+ # Extract the current flagtype object from the object.
+ my ($obj_flagtype) = grep { $_->id == $flagtype->id } @{$obj->flag_types};
+
+ # We cannot create a new flag if there is already one and this
+ # flag type is not multiplicable.
+ if (!$flagtype->is_multiplicable) {
+ if (scalar @{$obj_flagtype->{flags}}) {
+ ThrowUserError('flag_type_not_multiplicable', {type => $flagtype});
+ }
}
- if ($obj_flag
- && $requestee_changed
- && $obj_flag->requestee_id
- && $obj_flag->requestee->setting('requestee_cc') eq 'on'
- && $bug->reporter->id != $obj_flag->requestee->id)
- {
- $bug->add_cc($obj_flag->requestee);
- }
+ ($obj_flag, $requestee_changed)
+ = $class->_validate(undef, $obj_flagtype, $params, $bug, $attachment);
+ }
+ else {
+ ThrowCodeError('param_required',
+ {function => $class . '->set_flag', param => 'id/type_id'});
+ }
+
+ if ( $obj_flag
+ && $requestee_changed
+ && $obj_flag->requestee_id
+ && $obj_flag->requestee->setting('requestee_cc') eq 'on'
+ && $bug->reporter->id != $obj_flag->requestee->id)
+ {
+ $bug->add_cc($obj_flag->requestee);
+ }
}
sub _validate {
- my ($class, $flag, $flag_type, $params, $bug, $attachment) = @_;
-
- # If it's a new flag, let's create it now.
- my $obj_flag = $flag || bless({ type_id => $flag_type->id,
- status => '',
- bug_id => $bug->id,
- attach_id => $attachment ?
- $attachment->id : undef},
- $class);
-
- my $old_status = $obj_flag->status;
- my $old_requestee_id = $obj_flag->requestee_id;
-
- $obj_flag->_set_status($params->{status});
- $obj_flag->_set_requestee($params->{requestee}, $bug, $attachment, $params->{skip_roe});
-
- # The requestee ID can be undefined.
- my $requestee_changed = ($obj_flag->requestee_id || 0) != ($old_requestee_id || 0);
-
- # The setter field MUST NOT be updated if neither the status
- # nor the requestee fields changed.
- if (($obj_flag->status ne $old_status) || $requestee_changed) {
- $obj_flag->_set_setter($params->{setter});
- }
+ my ($class, $flag, $flag_type, $params, $bug, $attachment) = @_;
- # If the flag is deleted, remove it from the list.
- if ($obj_flag->status eq 'X') {
- @{$flag_type->{flags}} = grep { $_->id != $obj_flag->id } @{$flag_type->{flags}};
- return;
- }
- # Add the newly created flag to the list.
- elsif (!$obj_flag->id) {
- push(@{$flag_type->{flags}}, $obj_flag);
- }
- return wantarray ? ($obj_flag, $requestee_changed) : $obj_flag;
+ # If it's a new flag, let's create it now.
+ my $obj_flag = $flag || bless(
+ {
+ type_id => $flag_type->id,
+ status => '',
+ bug_id => $bug->id,
+ attach_id => $attachment ? $attachment->id : undef
+ },
+ $class
+ );
+
+ my $old_status = $obj_flag->status;
+ my $old_requestee_id = $obj_flag->requestee_id;
+
+ $obj_flag->_set_status($params->{status});
+ $obj_flag->_set_requestee($params->{requestee}, $bug, $attachment,
+ $params->{skip_roe});
+
+ # The requestee ID can be undefined.
+ my $requestee_changed
+ = ($obj_flag->requestee_id || 0) != ($old_requestee_id || 0);
+
+ # The setter field MUST NOT be updated if neither the status
+ # nor the requestee fields changed.
+ if (($obj_flag->status ne $old_status) || $requestee_changed) {
+ $obj_flag->_set_setter($params->{setter});
+ }
+
+ # If the flag is deleted, remove it from the list.
+ if ($obj_flag->status eq 'X') {
+ @{$flag_type->{flags}}
+ = grep { $_->id != $obj_flag->id } @{$flag_type->{flags}};
+ return;
+ }
+
+ # Add the newly created flag to the list.
+ elsif (!$obj_flag->id) {
+ push(@{$flag_type->{flags}}, $obj_flag);
+ }
+ return wantarray ? ($obj_flag, $requestee_changed) : $obj_flag;
}
=pod
@@ -441,153 +451,163 @@ Creates a flag record in the database.
=cut
sub create {
- my ($class, $flag, $timestamp) = @_;
- $timestamp ||= Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ my ($class, $flag, $timestamp) = @_;
+ $timestamp ||= Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
- my $params = {};
- my @columns = grep { $_ ne 'id' } $class->_get_db_columns;
+ my $params = {};
+ my @columns = grep { $_ ne 'id' } $class->_get_db_columns;
- # Some columns use date formatting so use alias instead
- @columns = map { /\s+AS\s+(.*)$/ ? $1 : $_ } @columns;
+ # Some columns use date formatting so use alias instead
+ @columns = map { /\s+AS\s+(.*)$/ ? $1 : $_ } @columns;
- $params->{$_} = $flag->{$_} foreach @columns;
+ $params->{$_} = $flag->{$_} foreach @columns;
- $params->{creation_date} = $params->{modification_date} = $timestamp;
+ $params->{creation_date} = $params->{modification_date} = $timestamp;
- $flag = $class->SUPER::create($params);
- return $flag;
+ $flag = $class->SUPER::create($params);
+ return $flag;
}
sub update {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
- my $timestamp = shift || $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
-
- my $changes = $self->SUPER::update(@_);
-
- if (scalar(keys %$changes)) {
- $dbh->do('UPDATE flags SET modification_date = ? WHERE id = ?',
- undef, ($timestamp, $self->id));
- $self->{'modification_date'} =
- format_time($timestamp, '%Y.%m.%d %T', Bugzilla->local_timezone);
- Bugzilla->memcached->clear({ table => 'flags', id => $self->id });
- }
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $timestamp = shift || $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+
+ my $changes = $self->SUPER::update(@_);
- # BMO - provide a hook which passes the flag object
- Bugzilla::Hook::process('flag_updated', {flag => $self, changes => $changes, timestamp => $timestamp});
+ if (scalar(keys %$changes)) {
+ $dbh->do('UPDATE flags SET modification_date = ? WHERE id = ?',
+ undef, ($timestamp, $self->id));
+ $self->{'modification_date'}
+ = format_time($timestamp, '%Y.%m.%d %T', Bugzilla->local_timezone);
+ Bugzilla->memcached->clear({table => 'flags', id => $self->id});
+ }
- return $changes;
+ # BMO - provide a hook which passes the flag object
+ Bugzilla::Hook::process('flag_updated',
+ {flag => $self, changes => $changes, timestamp => $timestamp});
+
+ return $changes;
}
sub snapshot {
- my ($class, $flags) = @_;
-
- my @summaries;
- foreach my $flag (@$flags) {
- my $summary = $flag->setter->nick . ':' . $flag->type->name . $flag->status;
- $summary .= "(" . $flag->requestee->login . ")" if $flag->requestee;
- push(@summaries, $summary);
- }
- return @summaries;
+ my ($class, $flags) = @_;
+
+ my @summaries;
+ foreach my $flag (@$flags) {
+ my $summary = $flag->setter->nick . ':' . $flag->type->name . $flag->status;
+ $summary .= "(" . $flag->requestee->login . ")" if $flag->requestee;
+ push(@summaries, $summary);
+ }
+ return @summaries;
}
sub update_activity {
- my ($class, $old_summaries, $new_summaries) = @_;
+ my ($class, $old_summaries, $new_summaries) = @_;
- my ($removed, $added) = diff_arrays($old_summaries, $new_summaries);
- if (scalar @$removed || scalar @$added) {
- # Remove flag requester/setter information
- foreach (@$removed, @$added) { s/^[^:]+:// }
+ my ($removed, $added) = diff_arrays($old_summaries, $new_summaries);
+ if (scalar @$removed || scalar @$added) {
- $removed = join(", ", @$removed);
- $added = join(", ", @$added);
- return ($removed, $added);
- }
- return ();
+ # Remove flag requester/setter information
+ foreach (@$removed, @$added) {s/^[^:]+://}
+
+ $removed = join(", ", @$removed);
+ $added = join(", ", @$added);
+ return ($removed, $added);
+ }
+ return ();
}
sub update_flags {
- my ($class, $self, $old_self, $timestamp) = @_;
-
- my @old_summaries = $class->snapshot($old_self->flags);
- my %old_flags = map { $_->id => $_ } @{$old_self->flags};
-
- foreach my $new_flag (@{$self->flags}) {
- if (!$new_flag->id) {
- # This is a new flag.
- my $flag = $class->create($new_flag, $timestamp);
- $new_flag->{id} = $flag->id;
- $new_flag->{creation_date} = format_time($timestamp, '%Y.%m.%d %H:%i:%s');
- $new_flag->{modification_date} = format_time($timestamp, '%Y.%m.%d %H:%i:%s');
- $class->notify($new_flag, undef, $self, $timestamp);
- }
- else {
- my $changes = $new_flag->update($timestamp);
- if (scalar(keys %$changes)) {
- $class->notify($new_flag, $old_flags{$new_flag->id}, $self, $timestamp);
- }
- delete $old_flags{$new_flag->id};
- }
+ my ($class, $self, $old_self, $timestamp) = @_;
+
+ my @old_summaries = $class->snapshot($old_self->flags);
+ my %old_flags = map { $_->id => $_ } @{$old_self->flags};
+
+ foreach my $new_flag (@{$self->flags}) {
+ if (!$new_flag->id) {
+
+ # This is a new flag.
+ my $flag = $class->create($new_flag, $timestamp);
+ $new_flag->{id} = $flag->id;
+ $new_flag->{creation_date} = format_time($timestamp, '%Y.%m.%d %H:%i:%s');
+ $new_flag->{modification_date} = format_time($timestamp, '%Y.%m.%d %H:%i:%s');
+ $class->notify($new_flag, undef, $self, $timestamp);
}
- # These flags have been deleted.
- foreach my $old_flag (values %old_flags) {
- $class->notify(undef, $old_flag, $self, $timestamp);
-
- # BMO - provide a hook which passes the timestamp,
- # because that isn't passed to remove_from_db().
- Bugzilla::Hook::process('flag_deleted', {flag => $old_flag, timestamp => $timestamp});
- $old_flag->remove_from_db();
+ else {
+ my $changes = $new_flag->update($timestamp);
+ if (scalar(keys %$changes)) {
+ $class->notify($new_flag, $old_flags{$new_flag->id}, $self, $timestamp);
+ }
+ delete $old_flags{$new_flag->id};
}
-
- # If the bug has been moved into another product or component,
- # we must also take care of attachment flags which are no longer valid,
- # as well as all bug flags which haven't been forgotten above.
- if ($self->isa('Bugzilla::Bug')
- && ($self->{_old_product_name} || $self->{_old_component_name}))
+ }
+
+ # These flags have been deleted.
+ foreach my $old_flag (values %old_flags) {
+ $class->notify(undef, $old_flag, $self, $timestamp);
+
+ # BMO - provide a hook which passes the timestamp,
+ # because that isn't passed to remove_from_db().
+ Bugzilla::Hook::process('flag_deleted',
+ {flag => $old_flag, timestamp => $timestamp});
+ $old_flag->remove_from_db();
+ }
+
+ # If the bug has been moved into another product or component,
+ # we must also take care of attachment flags which are no longer valid,
+ # as well as all bug flags which haven't been forgotten above.
+ if ($self->isa('Bugzilla::Bug')
+ && ($self->{_old_product_name} || $self->{_old_component_name}))
+ {
+ my @removed = $class->force_cleanup($self);
+ push(@old_summaries, @removed);
+ }
+
+ my @new_summaries = $class->snapshot($self->flags);
+ my @changes = $class->update_activity(\@old_summaries, \@new_summaries);
+
+ Bugzilla::Hook::process(
+ 'flag_end_of_update',
{
- my @removed = $class->force_cleanup($self);
- push(@old_summaries, @removed);
+ object => $self,
+ timestamp => $timestamp,
+ old_flags => \@old_summaries,
+ new_flags => \@new_summaries,
}
-
- my @new_summaries = $class->snapshot($self->flags);
- my @changes = $class->update_activity(\@old_summaries, \@new_summaries);
-
- Bugzilla::Hook::process('flag_end_of_update', { object => $self,
- timestamp => $timestamp,
- old_flags => \@old_summaries,
- new_flags => \@new_summaries,
- });
- return @changes;
+ );
+ return @changes;
}
sub retarget {
- my ($self, $obj) = @_;
-
- my @flagtypes = grep { $_->name eq $self->type->name } @{$obj->flag_types};
-
- my $success = 0;
- foreach my $flagtype (@flagtypes) {
- next if !$flagtype->is_active;
- next if (!$flagtype->is_multiplicable && scalar @{$flagtype->{flags}});
- next unless (($self->status eq '?' && $self->setter->can_request_flag($flagtype))
- || $self->setter->can_set_flag($flagtype));
-
- $self->{type_id} = $flagtype->id;
- delete $self->{type};
- $success = 1;
- last;
- }
- return $success;
+ my ($self, $obj) = @_;
+
+ my @flagtypes = grep { $_->name eq $self->type->name } @{$obj->flag_types};
+
+ my $success = 0;
+ foreach my $flagtype (@flagtypes) {
+ next if !$flagtype->is_active;
+ next if (!$flagtype->is_multiplicable && scalar @{$flagtype->{flags}});
+ next
+ unless (($self->status eq '?' && $self->setter->can_request_flag($flagtype))
+ || $self->setter->can_set_flag($flagtype));
+
+ $self->{type_id} = $flagtype->id;
+ delete $self->{type};
+ $success = 1;
+ last;
+ }
+ return $success;
}
# In case the bug's product/component has changed, clear flags that are
# no longer valid.
sub force_cleanup {
- my ($class, $bug) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($class, $bug) = @_;
+ my $dbh = Bugzilla->dbh;
- my $flag_ids = $dbh->selectcol_arrayref(
- 'SELECT DISTINCT flags.id
+ my $flag_ids = $dbh->selectcol_arrayref(
+ 'SELECT DISTINCT flags.id
FROM flags
INNER JOIN bugs
ON flags.bug_id = bugs.bug_id
@@ -595,53 +615,56 @@ sub force_cleanup {
ON flags.type_id = i.type_id
AND (bugs.product_id = i.product_id OR i.product_id IS NULL)
AND (bugs.component_id = i.component_id OR i.component_id IS NULL)
- WHERE bugs.bug_id = ? AND i.type_id IS NULL',
- undef, $bug->id);
+ WHERE bugs.bug_id = ? AND i.type_id IS NULL', undef, $bug->id
+ );
- my @removed = $class->force_retarget($flag_ids, $bug);
+ my @removed = $class->force_retarget($flag_ids, $bug);
- $flag_ids = $dbh->selectcol_arrayref(
- 'SELECT DISTINCT flags.id
+ $flag_ids = $dbh->selectcol_arrayref(
+ 'SELECT DISTINCT flags.id
FROM flags, bugs, flagexclusions e
WHERE bugs.bug_id = ?
AND flags.bug_id = bugs.bug_id
AND flags.type_id = e.type_id
AND (bugs.product_id = e.product_id OR e.product_id IS NULL)
AND (bugs.component_id = e.component_id OR e.component_id IS NULL)',
- undef, $bug->id);
+ undef, $bug->id
+ );
- push(@removed , $class->force_retarget($flag_ids, $bug));
- return @removed;
+ push(@removed, $class->force_retarget($flag_ids, $bug));
+ return @removed;
}
sub force_retarget {
- my ($class, $flag_ids, $bug) = @_;
- my $dbh = Bugzilla->dbh;
-
- my $flags = $class->new_from_list($flag_ids);
- my @removed;
- foreach my $flag (@$flags) {
- # $bug is undefined when e.g. editing inclusion and exclusion lists.
- my $obj = $flag->attachment || $bug || $flag->bug;
- my $is_retargetted = $flag->retarget($obj);
- if ($is_retargetted) {
- $dbh->do('UPDATE flags SET type_id = ? WHERE id = ?',
- undef, ($flag->type_id, $flag->id));
- Bugzilla->memcached->clear({ table => 'flags', id => $flag->id });
- }
- else {
- # Track deleted attachment flags.
- push(@removed, $class->snapshot([$flag])) if $flag->attach_id;
- $class->notify(undef, $flag, $bug || $flag->bug);
-
- # BMO - provide a hook which passes the timestamp,
- # because that isn't passed to remove_from_db().
- my ($timestamp) = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
- Bugzilla::Hook::process('flag_deleted', {flag => $flag, timestamp => $timestamp});
- $flag->remove_from_db();
- }
+ my ($class, $flag_ids, $bug) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $flags = $class->new_from_list($flag_ids);
+ my @removed;
+ foreach my $flag (@$flags) {
+
+ # $bug is undefined when e.g. editing inclusion and exclusion lists.
+ my $obj = $flag->attachment || $bug || $flag->bug;
+ my $is_retargetted = $flag->retarget($obj);
+ if ($is_retargetted) {
+ $dbh->do('UPDATE flags SET type_id = ? WHERE id = ?',
+ undef, ($flag->type_id, $flag->id));
+ Bugzilla->memcached->clear({table => 'flags', id => $flag->id});
+ }
+ else {
+ # Track deleted attachment flags.
+ push(@removed, $class->snapshot([$flag])) if $flag->attach_id;
+ $class->notify(undef, $flag, $bug || $flag->bug);
+
+ # BMO - provide a hook which passes the timestamp,
+ # because that isn't passed to remove_from_db().
+ my ($timestamp) = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ Bugzilla::Hook::process('flag_deleted',
+ {flag => $flag, timestamp => $timestamp});
+ $flag->remove_from_db();
}
- return @removed;
+ }
+ return @removed;
}
###############################
@@ -649,153 +672,168 @@ sub force_retarget {
###############################
sub _set_requestee {
- my ($self, $requestee, $bug, $attachment, $skip_requestee_on_error) = @_;
+ my ($self, $requestee, $bug, $attachment, $skip_requestee_on_error) = @_;
- $self->{requestee} =
- $self->_check_requestee($requestee, $bug, $attachment, $skip_requestee_on_error);
+ $self->{requestee} = $self->_check_requestee($requestee, $bug, $attachment,
+ $skip_requestee_on_error);
- $self->{requestee_id} =
- $self->{requestee} ? $self->{requestee}->id : undef;
+ $self->{requestee_id} = $self->{requestee} ? $self->{requestee}->id : undef;
}
sub _set_setter {
- my ($self, $setter) = @_;
+ my ($self, $setter) = @_;
- $self->set('setter', $setter);
- $self->{setter_id} = $self->setter->id;
+ $self->set('setter', $setter);
+ $self->{setter_id} = $self->setter->id;
}
sub _set_status {
- my ($self, $status) = @_;
+ my ($self, $status) = @_;
- # Store the old flag status. It's needed by _check_setter().
- $self->{_old_status} = $self->status;
- $self->set('status', $status);
+ # Store the old flag status. It's needed by _check_setter().
+ $self->{_old_status} = $self->status;
+ $self->set('status', $status);
}
sub _check_requestee {
- my ($self, $requestee, $bug, $attachment, $skip_requestee_on_error) = @_;
-
- # If the flag status is not "?", then no requestee can be defined.
- return undef if ($self->status ne '?');
-
- # Store this value before updating the flag object.
- my $old_requestee = $self->requestee ? $self->requestee->login : '';
-
- if ($self->status eq '?' && $requestee) {
- $requestee = Bugzilla::User->check($requestee);
+ my ($self, $requestee, $bug, $attachment, $skip_requestee_on_error) = @_;
+
+ # If the flag status is not "?", then no requestee can be defined.
+ return undef if ($self->status ne '?');
+
+ # Store this value before updating the flag object.
+ my $old_requestee = $self->requestee ? $self->requestee->login : '';
+
+ if ($self->status eq '?' && $requestee) {
+ $requestee = Bugzilla::User->check($requestee);
+ }
+ else {
+ undef $requestee;
+ }
+
+ if ($requestee && $requestee->login ne $old_requestee) {
+
+ # Make sure the user didn't specify a requestee unless the flag
+ # is specifically requestable. For existing flags, if the requestee
+ # was set before the flag became specifically unrequestable, the
+ # user can either remove him or leave him alone.
+ ThrowCodeError('flag_type_requestee_disabled', {type => $self->type})
+ if !$self->type->is_requesteeble;
+
+ # BMO customisation:
+ # You can't ask a disabled account, as they don't have the ability to
+ # set the flag.
+ ThrowUserError('flag_requestee_disabled', {requestee => $requestee})
+ if !$requestee->is_enabled;
+
+ # Make sure the requestee can see the bug.
+ # Note that can_see_bug() will query the DB, so if the bug
+ # is being added/removed from some groups and these changes
+ # haven't been committed to the DB yet, they won't be taken
+ # into account here. In this case, old group restrictions matter.
+ # However, if the user has just been changed to the assignee,
+ # qa_contact, or added to the cc list of the bug and the bug
+ # is cclist_accessible, the requestee is allowed.
+ if (
+ !$requestee->can_see_bug($self->bug_id)
+ && ( !$bug->cclist_accessible
+ || !grep($_->id == $requestee->id, @{$bug->cc_users})
+ && $requestee->id != $bug->assigned_to->id
+ && (!$bug->qa_contact || $requestee->id != $bug->qa_contact->id))
+ )
+ {
+ if ($skip_requestee_on_error) {
+ undef $requestee;
+ }
+ else {
+ ThrowUserError(
+ 'flag_requestee_unauthorized',
+ {
+ flag_type => $self->type,
+ requestee => $requestee,
+ bug_id => $self->bug_id,
+ attach_id => $self->attach_id
+ }
+ );
+ }
}
- else {
+
+ # Make sure the requestee can see the private attachment.
+ elsif ($self->attach_id && $attachment->isprivate && !$requestee->is_insider) {
+ if ($skip_requestee_on_error) {
undef $requestee;
+ }
+ else {
+ ThrowUserError(
+ 'flag_requestee_unauthorized_attachment',
+ {
+ flag_type => $self->type,
+ requestee => $requestee,
+ bug_id => $self->bug_id,
+ attach_id => $self->attach_id
+ }
+ );
+ }
}
- if ($requestee && $requestee->login ne $old_requestee) {
- # Make sure the user didn't specify a requestee unless the flag
- # is specifically requestable. For existing flags, if the requestee
- # was set before the flag became specifically unrequestable, the
- # user can either remove him or leave him alone.
- ThrowCodeError('flag_type_requestee_disabled', { type => $self->type })
- if !$self->type->is_requesteeble;
-
- # BMO customisation:
- # You can't ask a disabled account, as they don't have the ability to
- # set the flag.
- ThrowUserError('flag_requestee_disabled', { requestee => $requestee })
- if !$requestee->is_enabled;
-
- # Make sure the requestee can see the bug.
- # Note that can_see_bug() will query the DB, so if the bug
- # is being added/removed from some groups and these changes
- # haven't been committed to the DB yet, they won't be taken
- # into account here. In this case, old group restrictions matter.
- # However, if the user has just been changed to the assignee,
- # qa_contact, or added to the cc list of the bug and the bug
- # is cclist_accessible, the requestee is allowed.
- if (!$requestee->can_see_bug($self->bug_id)
- && (!$bug->cclist_accessible
- || !grep($_->id == $requestee->id, @{ $bug->cc_users })
- && $requestee->id != $bug->assigned_to->id
- && (!$bug->qa_contact || $requestee->id != $bug->qa_contact->id)))
- {
- if ($skip_requestee_on_error) {
- undef $requestee;
- }
- else {
- ThrowUserError('flag_requestee_unauthorized',
- { flag_type => $self->type,
- requestee => $requestee,
- bug_id => $self->bug_id,
- attach_id => $self->attach_id });
- }
- }
- # Make sure the requestee can see the private attachment.
- elsif ($self->attach_id && $attachment->isprivate && !$requestee->is_insider) {
- if ($skip_requestee_on_error) {
- undef $requestee;
- }
- else {
- ThrowUserError('flag_requestee_unauthorized_attachment',
- { flag_type => $self->type,
- requestee => $requestee,
- bug_id => $self->bug_id,
- attach_id => $self->attach_id });
- }
- }
- # Make sure the user is allowed to set the flag.
- elsif (!$requestee->can_set_flag($self->type)) {
- if ($skip_requestee_on_error) {
- undef $requestee;
- }
- else {
- ThrowUserError('flag_requestee_needs_privs',
- {'requestee' => $requestee,
- 'flagtype' => $self->type});
- }
- }
+ # Make sure the user is allowed to set the flag.
+ elsif (!$requestee->can_set_flag($self->type)) {
+ if ($skip_requestee_on_error) {
+ undef $requestee;
+ }
+ else {
+ ThrowUserError('flag_requestee_needs_privs',
+ {'requestee' => $requestee, 'flagtype' => $self->type});
+ }
}
- return $requestee;
+ }
+ return $requestee;
}
sub _check_setter {
- my ($self, $setter) = @_;
-
- # By default, the currently logged in user is the setter.
- $setter ||= Bugzilla->user;
- (blessed($setter) && $setter->isa('Bugzilla::User') && $setter->id)
- || ThrowCodeError('invalid_user');
-
- # set_status() has already been called. So this refers
- # to the new flag status.
- my $status = $self->status;
-
- ThrowUserError('flag_update_denied',
- { name => $self->type->name,
- status => $status,
- old_status => $self->{_old_status} })
- unless $setter->can_change_flag($self->type, $self->{_old_status} || 'X', $status);
-
- # If the request is being retargetted, we don't update
- # the setter, so that the setter gets the notification.
- if ($status eq '?' && $self->{_old_status} eq '?') {
- return $self->setter;
+ my ($self, $setter) = @_;
+
+ # By default, the currently logged in user is the setter.
+ $setter ||= Bugzilla->user;
+ (blessed($setter) && $setter->isa('Bugzilla::User') && $setter->id)
+ || ThrowCodeError('invalid_user');
+
+ # set_status() has already been called. So this refers
+ # to the new flag status.
+ my $status = $self->status;
+
+ ThrowUserError(
+ 'flag_update_denied',
+ {
+ name => $self->type->name,
+ status => $status,
+ old_status => $self->{_old_status}
}
- return $setter;
+ )
+ unless $setter->can_change_flag($self->type, $self->{_old_status} || 'X',
+ $status);
+
+ # If the request is being retargetted, we don't update
+ # the setter, so that the setter gets the notification.
+ if ($status eq '?' && $self->{_old_status} eq '?') {
+ return $self->setter;
+ }
+ return $setter;
}
sub _check_status {
- my ($self, $status) = @_;
-
- # - Make sure the status is valid.
- # - Make sure the user didn't request the flag unless it's requestable.
- # If the flag existed and was requested before it became unrequestable,
- # leave it as is.
- if (!grep($status eq $_ , qw(X + - ?))
- || ($status eq '?' && $self->status ne '?' && !$self->type->is_requestable))
- {
- ThrowUserError('flag_status_invalid', { id => $self->id,
- status => $status });
- }
- return $status;
+ my ($self, $status) = @_;
+
+ # - Make sure the status is valid.
+ # - Make sure the user didn't request the flag unless it's requestable.
+ # If the flag existed and was requested before it became unrequestable,
+ # leave it as is.
+ if (!grep($status eq $_, qw(X + - ?))
+ || ($status eq '?' && $self->status ne '?' && !$self->type->is_requestable))
+ {
+ ThrowUserError('flag_status_invalid', {id => $self->id, status => $status});
+ }
+ return $status;
}
######################################################################
@@ -816,128 +854,146 @@ array of hashes. This array is then passed to Flag::create().
=cut
sub extract_flags_from_cgi {
- my ($class, $bug, $attachment, $vars, $skip) = @_;
- my $cgi = Bugzilla->cgi;
-
- my $match_status = Bugzilla::User::match_field({
- '^requestee(_type)?-(\d+)$' => { 'type' => 'multi' },
- }, undef, $skip);
-
- $vars->{'match_field'} = 'requestee';
- if ($match_status == USER_MATCH_FAILED) {
- $vars->{'message'} = 'user_match_failed';
- }
- elsif ($match_status == USER_MATCH_MULTIPLE) {
- $vars->{'message'} = 'user_match_multiple';
+ my ($class, $bug, $attachment, $vars, $skip) = @_;
+ my $cgi = Bugzilla->cgi;
+
+ my $match_status
+ = Bugzilla::User::match_field(
+ {'^requestee(_type)?-(\d+)$' => {'type' => 'multi'},},
+ undef, $skip);
+
+ $vars->{'match_field'} = 'requestee';
+ if ($match_status == USER_MATCH_FAILED) {
+ $vars->{'message'} = 'user_match_failed';
+ }
+ elsif ($match_status == USER_MATCH_MULTIPLE) {
+ $vars->{'message'} = 'user_match_multiple';
+ }
+
+ # Extract a list of flag type IDs from field names.
+ my @flagtype_ids = map(/^flag_type-(\d+)$/ ? $1 : (), $cgi->param());
+ @flagtype_ids = grep($cgi->param("flag_type-$_") ne 'X', @flagtype_ids);
+
+ # Extract a list of existing flag IDs.
+ my @flag_ids = map(/^flag-(\d+)$/ ? $1 : (), $cgi->param());
+
+ return ([], []) unless (scalar(@flagtype_ids) || scalar(@flag_ids));
+
+ my (@new_flags, @flags);
+ foreach my $flag_id (@flag_ids) {
+ my $flag = $class->new($flag_id);
+
+ # If the flag no longer exists, ignore it.
+ next unless $flag;
+
+ my $status = $cgi->param("flag-$flag_id");
+
+ # If the user entered more than one name into the requestee field
+ # (i.e. they want more than one person to set the flag) we can reuse
+ # the existing flag for the first person (who may well be the existing
+ # requestee), but we have to create new flags for each additional requestee.
+ my @requestees = $cgi->param("requestee-$flag_id");
+ my $requestee_email;
+ if ($status eq "?" && scalar(@requestees) > 1 && $flag->type->is_multiplicable)
+ {
+ # The first person, for which we'll reuse the existing flag.
+ $requestee_email = shift(@requestees);
+
+ # Create new flags like the existing one for each additional person.
+ foreach my $login (@requestees) {
+ push(
+ @new_flags,
+ {
+ type_id => $flag->type_id,
+ status => "?",
+ requestee => $login,
+ skip_roe => $skip
+ }
+ );
+ }
}
+ elsif ($status eq "?" && scalar(@requestees)) {
- # Extract a list of flag type IDs from field names.
- my @flagtype_ids = map(/^flag_type-(\d+)$/ ? $1 : (), $cgi->param());
- @flagtype_ids = grep($cgi->param("flag_type-$_") ne 'X', @flagtype_ids);
-
- # Extract a list of existing flag IDs.
- my @flag_ids = map(/^flag-(\d+)$/ ? $1 : (), $cgi->param());
-
- return ([], []) unless (scalar(@flagtype_ids) || scalar(@flag_ids));
-
- my (@new_flags, @flags);
- foreach my $flag_id (@flag_ids) {
- my $flag = $class->new($flag_id);
- # If the flag no longer exists, ignore it.
- next unless $flag;
-
- my $status = $cgi->param("flag-$flag_id");
-
- # If the user entered more than one name into the requestee field
- # (i.e. they want more than one person to set the flag) we can reuse
- # the existing flag for the first person (who may well be the existing
- # requestee), but we have to create new flags for each additional requestee.
- my @requestees = $cgi->param("requestee-$flag_id");
- my $requestee_email;
- if ($status eq "?"
- && scalar(@requestees) > 1
- && $flag->type->is_multiplicable)
- {
- # The first person, for which we'll reuse the existing flag.
- $requestee_email = shift(@requestees);
-
- # Create new flags like the existing one for each additional person.
- foreach my $login (@requestees) {
- push(@new_flags, { type_id => $flag->type_id,
- status => "?",
- requestee => $login,
- skip_roe => $skip });
- }
- }
- elsif ($status eq "?" && scalar(@requestees)) {
- # If there are several requestees and the flag type is not multiplicable,
- # this will fail. But that's the job of the validator to complain. All
- # we do here is to extract and convert data from the CGI.
- $requestee_email = trim($cgi->param("requestee-$flag_id") || '');
- }
-
- push(@flags, { id => $flag_id,
- status => $status,
- requestee => $requestee_email,
- skip_roe => $skip });
+ # If there are several requestees and the flag type is not multiplicable,
+ # this will fail. But that's the job of the validator to complain. All
+ # we do here is to extract and convert data from the CGI.
+ $requestee_email = trim($cgi->param("requestee-$flag_id") || '');
}
- # Get a list of active flag types available for this product/component.
- my $flag_types = Bugzilla::FlagType::match(
- { 'product_id' => $bug->{'product_id'},
- 'component_id' => $bug->{'component_id'},
- 'is_active' => 1 });
-
- foreach my $flagtype_id (@flagtype_ids) {
- # Checks if there are unexpected flags for the product/component.
- if (!scalar(grep { $_->id == $flagtype_id } @$flag_types)) {
- $vars->{'message'} = 'unexpected_flag_types';
- last;
- }
+ push(
+ @flags,
+ {
+ id => $flag_id,
+ status => $status,
+ requestee => $requestee_email,
+ skip_roe => $skip
+ }
+ );
+ }
+
+ # Get a list of active flag types available for this product/component.
+ my $flag_types = Bugzilla::FlagType::match({
+ 'product_id' => $bug->{'product_id'},
+ 'component_id' => $bug->{'component_id'},
+ 'is_active' => 1
+ });
+
+ foreach my $flagtype_id (@flagtype_ids) {
+
+ # Checks if there are unexpected flags for the product/component.
+ if (!scalar(grep { $_->id == $flagtype_id } @$flag_types)) {
+ $vars->{'message'} = 'unexpected_flag_types';
+ last;
}
-
- foreach my $flag_type (@$flag_types) {
- my $type_id = $flag_type->id;
-
- # Bug flags are only valid for bugs, and attachment flags are
- # only valid for attachments. So don't mix both.
- next unless ($flag_type->target_type eq 'bug' xor $attachment);
-
- # We are only interested in flags the user tries to create.
- next unless scalar(grep { $_ == $type_id } @flagtype_ids);
-
- # Get the number of flags of this type already set for this target.
- my $has_flags = $class->count(
- { 'type_id' => $type_id,
- 'target_type' => $attachment ? 'attachment' : 'bug',
- 'bug_id' => $bug->bug_id,
- 'attach_id' => $attachment ? $attachment->id : undef });
-
- # Do not create a new flag of this type if this flag type is
- # not multiplicable and already has a flag set.
- next if (!$flag_type->is_multiplicable && $has_flags);
-
- my $status = $cgi->param("flag_type-$type_id");
- trick_taint($status);
-
- my @logins = $cgi->param("requestee_type-$type_id");
- if ($status eq "?" && scalar(@logins)) {
- foreach my $login (@logins) {
- push (@new_flags, { type_id => $type_id,
- status => $status,
- requestee => $login,
- skip_roe => $skip });
- last unless $flag_type->is_multiplicable;
- }
- }
- else {
- push (@new_flags, { type_id => $type_id,
- status => $status });
- }
+ }
+
+ foreach my $flag_type (@$flag_types) {
+ my $type_id = $flag_type->id;
+
+ # Bug flags are only valid for bugs, and attachment flags are
+ # only valid for attachments. So don't mix both.
+ next unless ($flag_type->target_type eq 'bug' xor $attachment);
+
+ # We are only interested in flags the user tries to create.
+ next unless scalar(grep { $_ == $type_id } @flagtype_ids);
+
+ # Get the number of flags of this type already set for this target.
+ my $has_flags = $class->count({
+ 'type_id' => $type_id,
+ 'target_type' => $attachment ? 'attachment' : 'bug',
+ 'bug_id' => $bug->bug_id,
+ 'attach_id' => $attachment ? $attachment->id : undef
+ });
+
+ # Do not create a new flag of this type if this flag type is
+ # not multiplicable and already has a flag set.
+ next if (!$flag_type->is_multiplicable && $has_flags);
+
+ my $status = $cgi->param("flag_type-$type_id");
+ trick_taint($status);
+
+ my @logins = $cgi->param("requestee_type-$type_id");
+ if ($status eq "?" && scalar(@logins)) {
+ foreach my $login (@logins) {
+ push(
+ @new_flags,
+ {
+ type_id => $type_id,
+ status => $status,
+ requestee => $login,
+ skip_roe => $skip
+ }
+ );
+ last unless $flag_type->is_multiplicable;
+ }
+ }
+ else {
+ push(@new_flags, {type_id => $type_id, status => $status});
}
+ }
- # Return the list of flags to update and/or to create.
- return (\@flags, \@new_flags);
+ # Return the list of flags to update and/or to create.
+ return (\@flags, \@new_flags);
}
=pod
@@ -954,118 +1010,126 @@ or deleted.
=cut
sub notify {
- my ($class, $flag, $old_flag, $obj, $timestamp) = @_;
-
- if ($disable_flagmail) {
- return;
- }
-
- my ($bug, $attachment);
- if (blessed($obj) && $obj->isa('Bugzilla::Attachment')) {
- $attachment = $obj;
- $bug = $attachment->bug;
+ my ($class, $flag, $old_flag, $obj, $timestamp) = @_;
+
+ if ($disable_flagmail) {
+ return;
+ }
+
+ my ($bug, $attachment);
+ if (blessed($obj) && $obj->isa('Bugzilla::Attachment')) {
+ $attachment = $obj;
+ $bug = $attachment->bug;
+ }
+ elsif (blessed($obj) && $obj->isa('Bugzilla::Bug')) {
+ $bug = $obj;
+ }
+ else {
+ # Not a good time to throw an error.
+ return;
+ }
+
+ my $addressee;
+
+ # If the flag is set to '?', maybe the requestee wants a notification.
+ if ( $flag
+ && $flag->requestee_id
+ && (!$old_flag || ($old_flag->requestee_id || 0) != $flag->requestee_id))
+ {
+ if ($flag->requestee->wants_mail([EVT_FLAG_REQUESTED])) {
+ $addressee = $flag->requestee;
}
- elsif (blessed($obj) && $obj->isa('Bugzilla::Bug')) {
- $bug = $obj;
- }
- else {
- # Not a good time to throw an error.
- return;
- }
-
- my $addressee;
- # If the flag is set to '?', maybe the requestee wants a notification.
- if ($flag && $flag->requestee_id
- && (!$old_flag || ($old_flag->requestee_id || 0) != $flag->requestee_id))
- {
- if ($flag->requestee->wants_mail([EVT_FLAG_REQUESTED])) {
- $addressee = $flag->requestee;
- }
- }
- elsif ($old_flag && $old_flag->status eq '?'
- && (!$flag || $flag->status ne '?'))
- {
- if ($old_flag->setter->wants_mail([EVT_REQUESTED_FLAG])) {
- $addressee = $old_flag->setter;
- }
- }
-
- my $cc_list = $flag ? $flag->type->cc_list : $old_flag->type->cc_list;
- $cc_list //= '';
- # Is there someone to notify?
- return unless ($addressee || $cc_list);
-
- # The email client will display the Date: header in the desired timezone,
- # so we can always use UTC here.
- $timestamp ||= Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
- $timestamp = format_time($timestamp, '%a, %d %b %Y %T %z', 'UTC');
-
- # If the target bug is restricted to one or more groups, then we need
- # to make sure we don't send email about it to unauthorized users
- # on the request type's CC: list, so we have to trawl the list for users
- # not in those groups or email addresses that don't have an account.
- my @bug_in_groups = grep {$_->{'ison'} || $_->{'mandatory'}} @{$bug->groups};
- my $attachment_is_private = $attachment ? $attachment->isprivate : undef;
-
- my %recipients;
- foreach my $cc (split(/[, ]+/, $cc_list)) {
- my $ccuser = new Bugzilla::User({ name => $cc, cache => 1 });
- next if (scalar(@bug_in_groups) && (!$ccuser || !$ccuser->can_see_bug($bug->bug_id)));
- next if $attachment_is_private && (!$ccuser || !$ccuser->is_insider);
- # Prevent duplicated entries due to case sensitivity.
- $cc = $ccuser ? $ccuser->email : $cc;
- $recipients{$cc} = $ccuser;
- }
-
- # Only notify if the addressee is allowed to receive the email.
- if ($addressee && $addressee->email_enabled) {
- $recipients{$addressee->email} = $addressee;
- }
- # Process and send notification for each recipient.
- # If there are users in the CC list who don't have an account,
- # use the default language for email notifications.
- my $default_lang;
- if (grep { !$_ } values %recipients) {
- $default_lang = Bugzilla::User->new()->setting('lang');
- }
-
- # Get comments on the bug
- my $all_comments = $bug->comments({ after => $bug->lastdiffed });
- @$all_comments = grep { $_->type || $_->body =~ /\S/ } @$all_comments;
-
- # Get public only comments
- my $public_comments = [ grep { !$_->is_private } @$all_comments ];
-
- foreach my $to (keys %recipients) {
- # Add threadingmarker to allow flag notification emails to be the
- # threaded similar to normal bug change emails.
- my $thread_user_id = $recipients{$to} ? $recipients{$to}->id : 0;
-
- # We only want to show private comments to users in the is_insider group
- my $comments = $recipients{$to} && $recipients{$to}->is_insider
- ? $all_comments : $public_comments;
-
- my $vars = {
- flag => $flag,
- old_flag => $old_flag,
- to => $to,
- date => $timestamp,
- bug => $bug,
- attachment => $attachment,
- threadingmarker => build_thread_marker($bug->id, $thread_user_id),
- new_comments => $comments,
- };
-
- my $lang = $recipients{$to} ?
- $recipients{$to}->setting('lang') : $default_lang;
-
- my $template = Bugzilla->template_inner($lang);
- my $message;
- $template->process("request/email.txt.tmpl", $vars, \$message)
- || ThrowTemplateError($template->error());
-
- MessageToMTA($message);
+ }
+ elsif ($old_flag
+ && $old_flag->status eq '?'
+ && (!$flag || $flag->status ne '?'))
+ {
+ if ($old_flag->setter->wants_mail([EVT_REQUESTED_FLAG])) {
+ $addressee = $old_flag->setter;
}
+ }
+
+ my $cc_list = $flag ? $flag->type->cc_list : $old_flag->type->cc_list;
+ $cc_list //= '';
+
+ # Is there someone to notify?
+ return unless ($addressee || $cc_list);
+
+ # The email client will display the Date: header in the desired timezone,
+ # so we can always use UTC here.
+ $timestamp ||= Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ $timestamp = format_time($timestamp, '%a, %d %b %Y %T %z', 'UTC');
+
+ # If the target bug is restricted to one or more groups, then we need
+ # to make sure we don't send email about it to unauthorized users
+ # on the request type's CC: list, so we have to trawl the list for users
+ # not in those groups or email addresses that don't have an account.
+ my @bug_in_groups = grep { $_->{'ison'} || $_->{'mandatory'} } @{$bug->groups};
+ my $attachment_is_private = $attachment ? $attachment->isprivate : undef;
+
+ my %recipients;
+ foreach my $cc (split(/[, ]+/, $cc_list)) {
+ my $ccuser = new Bugzilla::User({name => $cc, cache => 1});
+ next
+ if (scalar(@bug_in_groups)
+ && (!$ccuser || !$ccuser->can_see_bug($bug->bug_id)));
+ next if $attachment_is_private && (!$ccuser || !$ccuser->is_insider);
+
+ # Prevent duplicated entries due to case sensitivity.
+ $cc = $ccuser ? $ccuser->email : $cc;
+ $recipients{$cc} = $ccuser;
+ }
+
+ # Only notify if the addressee is allowed to receive the email.
+ if ($addressee && $addressee->email_enabled) {
+ $recipients{$addressee->email} = $addressee;
+ }
+
+ # Process and send notification for each recipient.
+ # If there are users in the CC list who don't have an account,
+ # use the default language for email notifications.
+ my $default_lang;
+ if (grep { !$_ } values %recipients) {
+ $default_lang = Bugzilla::User->new()->setting('lang');
+ }
+
+ # Get comments on the bug
+ my $all_comments = $bug->comments({after => $bug->lastdiffed});
+ @$all_comments = grep { $_->type || $_->body =~ /\S/ } @$all_comments;
+
+ # Get public only comments
+ my $public_comments = [grep { !$_->is_private } @$all_comments];
+
+ foreach my $to (keys %recipients) {
+
+ # Add threadingmarker to allow flag notification emails to be the
+ # threaded similar to normal bug change emails.
+ my $thread_user_id = $recipients{$to} ? $recipients{$to}->id : 0;
+
+ # We only want to show private comments to users in the is_insider group
+ my $comments = $recipients{$to}
+ && $recipients{$to}->is_insider ? $all_comments : $public_comments;
+
+ my $vars = {
+ flag => $flag,
+ old_flag => $old_flag,
+ to => $to,
+ date => $timestamp,
+ bug => $bug,
+ attachment => $attachment,
+ threadingmarker => build_thread_marker($bug->id, $thread_user_id),
+ new_comments => $comments,
+ };
+
+ my $lang = $recipients{$to} ? $recipients{$to}->setting('lang') : $default_lang;
+
+ my $template = Bugzilla->template_inner($lang);
+ my $message;
+ $template->process("request/email.txt.tmpl", $vars, \$message)
+ || ThrowTemplateError($template->error());
+
+ MessageToMTA($message);
+ }
}
# This is an internal function used by $bug->flag_types
@@ -1073,39 +1137,42 @@ sub notify {
# flag types and existing flags set on them. You should never
# call this function directly.
sub _flag_types {
- my ($class, $vars) = @_;
-
- my $target_type = $vars->{target_type};
- my $flags;
-
- # Retrieve all existing flags for this bug/attachment.
- if ($target_type eq 'bug') {
- my $bug_id = delete $vars->{bug_id};
- $flags = $class->match({target_type => 'bug', bug_id => $bug_id});
- }
- elsif ($target_type eq 'attachment') {
- my $attach_id = delete $vars->{attach_id};
- $flags = $class->match({attach_id => $attach_id});
- }
- else {
- ThrowCodeError('bad_arg', {argument => 'target_type',
- function => $class . '->_flag_types'});
- }
-
- # Get all available flag types for the given product and component.
- my $cache = Bugzilla->request_cache->{flag_types_per_component}->{$vars->{target_type}} ||= {};
- my $flag_data = $cache->{$vars->{component_id}} ||= Bugzilla::FlagType::match($vars);
- my $flag_types = dclone($flag_data);
-
- $_->{flags} = [] foreach @$flag_types;
- my %flagtypes = map { $_->id => $_ } @$flag_types;
-
- # Group existing flags per type, and skip those becoming invalid
- # (which can happen when a bug is being moved into a new product
- # or component).
- @$flags = grep { exists $flagtypes{$_->type_id} } @$flags;
- push(@{$flagtypes{$_->type_id}->{flags}}, $_) foreach @$flags;
- return $flag_types;
+ my ($class, $vars) = @_;
+
+ my $target_type = $vars->{target_type};
+ my $flags;
+
+ # Retrieve all existing flags for this bug/attachment.
+ if ($target_type eq 'bug') {
+ my $bug_id = delete $vars->{bug_id};
+ $flags = $class->match({target_type => 'bug', bug_id => $bug_id});
+ }
+ elsif ($target_type eq 'attachment') {
+ my $attach_id = delete $vars->{attach_id};
+ $flags = $class->match({attach_id => $attach_id});
+ }
+ else {
+ ThrowCodeError('bad_arg',
+ {argument => 'target_type', function => $class . '->_flag_types'});
+ }
+
+ # Get all available flag types for the given product and component.
+ my $cache
+ = Bugzilla->request_cache->{flag_types_per_component}->{$vars->{target_type}}
+ ||= {};
+ my $flag_data = $cache->{$vars->{component_id}}
+ ||= Bugzilla::FlagType::match($vars);
+ my $flag_types = dclone($flag_data);
+
+ $_->{flags} = [] foreach @$flag_types;
+ my %flagtypes = map { $_->id => $_ } @$flag_types;
+
+ # Group existing flags per type, and skip those becoming invalid
+ # (which can happen when a bug is being moved into a new product
+ # or component).
+ @$flags = grep { exists $flagtypes{$_->type_id} } @$flags;
+ push(@{$flagtypes{$_->type_id}->{flags}}, $_) foreach @$flags;
+ return $flag_types;
}
1;