From 8ec8da0491ad89604700b3e29a227966f6d84ba1 Mon Sep 17 00:00:00 2001 From: Perl Tidy Date: Wed, 5 Dec 2018 15:38:52 -0500 Subject: no bug - reformat all the code using the new perltidy rules --- Bugzilla/Flag.pm | 1531 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 799 insertions(+), 732 deletions(-) (limited to 'Bugzilla/Flag.pm') 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; -- cgit v1.2.3-24-g4f1b