summaryrefslogtreecommitdiffstats
path: root/Bugzilla/Flag.pm
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/Flag.pm')
-rw-r--r--Bugzilla/Flag.pm167
1 files changed, 119 insertions, 48 deletions
diff --git a/Bugzilla/Flag.pm b/Bugzilla/Flag.pm
index b687532c0..e87f1f674 100644
--- a/Bugzilla/Flag.pm
+++ b/Bugzilla/Flag.pm
@@ -81,15 +81,21 @@ use constant AUDIT_REMOVES => 0;
use constant SKIP_REQUESTEE_ON_ERROR => 1;
-use constant DB_COLUMNS => qw(
- id
- type_id
- bug_id
- attach_id
- requestee_id
- setter_id
- status
-);
+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';
+}
use constant UPDATE_COLUMNS => qw(
requestee_id
@@ -134,6 +140,14 @@ Returns the ID of the attachment this flag belongs to, if any.
Returns the status '+', '-', '?' of the flag.
+=item C<creation_date>
+
+Returns the timestamp when the flag was created.
+
+=item C<modification_date>
+
+Returns the timestamp when the flag was last modified.
+
=back
=cut
@@ -146,6 +160,8 @@ 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'}; }
###############################
#### Methods ####
@@ -180,22 +196,23 @@ is an attachment flag, else undefined.
sub type {
my $self = shift;
- $self->{'type'} ||= new Bugzilla::FlagType($self->{'type_id'});
- return $self->{'type'};
+ return $self->{'type'}
+ ||= new Bugzilla::FlagType($self->{'type_id'}, cache => 1 );
}
sub setter {
my $self = shift;
- $self->{'setter'} ||= new Bugzilla::User($self->{'setter_id'});
- return $self->{'setter'};
+ return $self->{'setter' }
+ ||= new Bugzilla::User({ id => $self->{'setter_id'}, cache => 1 });
}
sub requestee {
my $self = shift;
if (!defined $self->{'requestee'} && $self->{'requestee_id'}) {
- $self->{'requestee'} = new Bugzilla::User($self->{'requestee_id'});
+ $self->{'requestee'}
+ = new Bugzilla::User({ id => $self->{'requestee_id'}, cache => 1 });
}
return $self->{'requestee'};
}
@@ -205,16 +222,16 @@ sub attachment {
return undef unless $self->attach_id;
require Bugzilla::Attachment;
- $self->{'attachment'} ||= new Bugzilla::Attachment($self->attach_id);
- return $self->{'attachment'};
+ return $self->{'attachment'}
+ ||= new Bugzilla::Attachment({ id => $self->attach_id, cache => 1 });
}
sub bug {
my $self = shift;
require Bugzilla::Bug;
- $self->{'bug'} ||= new Bugzilla::Bug($self->bug_id);
- return $self->{'bug'};
+ return $self->{'bug'}
+ ||= new Bugzilla::Bug({ id => $self->bug_id, cache => 1 });
}
################################
@@ -284,7 +301,7 @@ sub count {
sub set_flag {
my ($class, $obj, $params) = @_;
- my ($bug, $attachment);
+ my ($bug, $attachment, $obj_flag, $requestee_changed);
if (blessed($obj) && $obj->isa('Bugzilla::Attachment')) {
$attachment = $obj;
$bug = $attachment->bug;
@@ -296,6 +313,12 @@ sub set_flag {
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} });
@@ -322,13 +345,14 @@ sub set_flag {
($obj_flagtype) = grep { $_->id == $flag->type_id } @{$obj->flag_types};
push(@{$obj_flagtype->{flags}}, $flag);
}
- my ($obj_flag) = grep { $_->id == $flag->id } @{$obj_flagtype->{flags}};
+ ($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;
- $class->_validate($obj_flag, $obj_flagtype, $params, $bug, $attachment);
+ ($obj_flag, $requestee_changed) =
+ $class->_validate($obj_flag, $obj_flagtype, $params, $bug, $attachment);
}
# Create a new flag.
elsif ($params->{type_id}) {
@@ -360,12 +384,21 @@ sub set_flag {
}
}
- $class->_validate(undef, $obj_flagtype, $params, $bug, $attachment);
+ ($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->add_cc($obj_flag->requestee);
+ }
}
sub _validate {
@@ -383,25 +416,27 @@ sub _validate {
my $old_requestee_id = $obj_flag->requestee_id;
$obj_flag->_set_status($params->{status});
- $obj_flag->_set_requestee($params->{requestee}, $attachment, $params->{skip_roe});
+ $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)
- # The requestee ID can be undefined.
- || (($obj_flag->requestee_id || 0) != ($old_requestee_id || 0)))
- {
+ 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
@@ -418,10 +453,14 @@ Creates a flag record in the database.
sub create {
my ($class, $flag, $timestamp) = @_;
- $timestamp ||= Bugzilla->dbh->selectrow_array('SELECT NOW()');
+ $timestamp ||= Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
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;
+
$params->{$_} = $flag->{$_} foreach @columns;
$params->{creation_date} = $params->{modification_date} = $timestamp;
@@ -433,14 +472,21 @@ sub create {
sub update {
my $self = shift;
my $dbh = Bugzilla->dbh;
- my $timestamp = shift || $dbh->selectrow_array('SELECT NOW()');
+ 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 });
}
+
+ # BMO - provide a hook which passes the flag object
+ Bugzilla::Hook::process('flag_updated', {flag => $self, changes => $changes, timestamp => $timestamp});
+
return $changes;
}
@@ -495,6 +541,10 @@ sub update_flags {
# 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();
}
@@ -586,11 +636,17 @@ sub force_retarget {
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();
}
}
@@ -602,10 +658,10 @@ sub force_retarget {
###############################
sub _set_requestee {
- my ($self, $requestee, $attachment, $skip_requestee_on_error) = @_;
+ my ($self, $requestee, $bug, $attachment, $skip_requestee_on_error) = @_;
$self->{requestee} =
- $self->_check_requestee($requestee, $attachment, $skip_requestee_on_error);
+ $self->_check_requestee($requestee, $bug, $attachment, $skip_requestee_on_error);
$self->{requestee_id} =
$self->{requestee} ? $self->{requestee}->id : undef;
@@ -627,7 +683,7 @@ sub _set_status {
}
sub _check_requestee {
- my ($self, $requestee, $attachment, $skip_requestee_on_error) = @_;
+ 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 '?');
@@ -647,15 +703,29 @@ sub _check_requestee {
# 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_requestee_disabled', { type => $self->type })
+ 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 restrictions matters.
- if (!$requestee->can_see_bug($self->bug_id)) {
+ # 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;
}
@@ -789,7 +859,7 @@ sub extract_flags_from_cgi {
# Extract a list of existing flag IDs.
my @flag_ids = map(/^flag-(\d+)$/ ? $1 : (), $cgi->param());
- return () if (!scalar(@flagtype_ids) && !scalar(@flag_ids));
+ return ([], []) unless (scalar(@flagtype_ids) || scalar(@flag_ids));
my (@new_flags, @flags);
foreach my $flag_id (@flag_ids) {
@@ -955,7 +1025,7 @@ sub notify {
my %recipients;
foreach my $cc (split(/[, ]+/, $cc_list)) {
- my $ccuser = new Bugzilla::User({ name => $cc });
+ 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.
@@ -1054,29 +1124,30 @@ sub _flag_types {
return $flag_types;
}
-=head1 SEE ALSO
+=head1 B<Methods in need of POD>
=over
-=item B<Bugzilla::FlagType>
+=item update_activity
-=back
+=item setter_id
+=item bug
-=head1 CONTRIBUTORS
+=item requestee_id
-=over
+=item DB_COLUMNS
-=item Myk Melez <myk@mozilla.org>
+=item set_flag
-=item Jouni Heikniemi <jouni@heikniemi.net>
+=item type_id
-=item Kevin Benton <kevin.benton@amd.com>
+=item snapshot
-=item Frédéric Buclin <LpSolit@gmail.com>
+=item update_flags
-=back
+=item update
-=cut
+=back
1;