summaryrefslogtreecommitdiffstats
path: root/Bugzilla/BugMail.pm
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/BugMail.pm')
-rw-r--r--Bugzilla/BugMail.pm1088
1 files changed, 555 insertions, 533 deletions
diff --git a/Bugzilla/BugMail.pm b/Bugzilla/BugMail.pm
index ebfc95d51..d5c1c4c95 100644
--- a/Bugzilla/BugMail.pm
+++ b/Bugzilla/BugMail.pm
@@ -27,16 +27,17 @@ use List::MoreUtils qw(uniq firstidx);
use Sys::Hostname;
use Storable qw(dclone);
-use constant BIT_DIRECT => 1;
-use constant BIT_WATCHING => 2;
+use constant BIT_DIRECT => 1;
+use constant BIT_WATCHING => 2;
sub relationships {
- my $ref = RELATIONSHIPS;
- # Clone it so that we don't modify the constant;
- my %relationships = %$ref;
- Bugzilla::Hook::process('bugmail_relationships',
- { relationships => \%relationships });
- return %relationships;
+ my $ref = RELATIONSHIPS;
+
+ # Clone it so that we don't modify the constant;
+ my %relationships = %$ref;
+ Bugzilla::Hook::process('bugmail_relationships',
+ {relationships => \%relationships});
+ return %relationships;
}
# This is a bit of a hack, basically keeping the old system()
@@ -49,505 +50,522 @@ sub relationships {
# All the names are email addresses, not userids
# values are scalars, except for cc, which is a list
sub Send {
- my ($id, $forced, $params) = @_;
- $params ||= {};
-
- my $dbh = Bugzilla->dbh;
- my $bug = new Bugzilla::Bug($id);
-
- my $start = $bug->lastdiffed;
- my $end = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
-
- # Bugzilla::User objects of people in various roles. More than one person
- # can 'have' a role, if the person in that role has changed, or people are
- # watching.
- my @assignees = ($bug->assigned_to);
- my @qa_contacts = $bug->qa_contact || ();
-
- my @ccs = @{ $bug->cc_users };
- # Include the people passed in as being in particular roles.
- # This can include people who used to hold those roles.
- # At this point, we don't care if there are duplicates in these arrays.
- my $changer = $forced->{'changer'};
- if ($forced->{'owner'}) {
- push (@assignees, Bugzilla::User->check($forced->{'owner'}));
- }
-
- if ($forced->{'qacontact'}) {
- push (@qa_contacts, Bugzilla::User->check($forced->{'qacontact'}));
- }
-
- if ($forced->{'cc'}) {
- foreach my $cc (@{$forced->{'cc'}}) {
- push(@ccs, Bugzilla::User->check($cc));
- }
- }
- my %user_cache = map { $_->id => $_ } (@assignees, @qa_contacts, @ccs);
-
- my @diffs;
- my @referenced_bugs;
- if (!$start) {
- @diffs = _get_new_bugmail_fields($bug);
- }
-
- if ($params->{dep_only}) {
- my $fields = Bugzilla->fields({ by_name => 1 });
- push(@diffs, { field_name => 'bug_status',
- field_desc => $fields->{bug_status}->description,
- old => $params->{changes}->{bug_status}->[0],
- new => $params->{changes}->{bug_status}->[1],
- login_name => $changer->login,
- blocker => $params->{blocker} },
- { field_name => 'resolution',
- field_desc => $fields->{resolution}->description,
- old => $params->{changes}->{resolution}->[0],
- new => $params->{changes}->{resolution}->[1],
- login_name => $changer->login,
- blocker => $params->{blocker} });
- push(@referenced_bugs, $params->{blocker}->id);
+ my ($id, $forced, $params) = @_;
+ $params ||= {};
+
+ my $dbh = Bugzilla->dbh;
+ my $bug = new Bugzilla::Bug($id);
+
+ my $start = $bug->lastdiffed;
+ my $end = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+
+ # Bugzilla::User objects of people in various roles. More than one person
+ # can 'have' a role, if the person in that role has changed, or people are
+ # watching.
+ my @assignees = ($bug->assigned_to);
+ my @qa_contacts = $bug->qa_contact || ();
+
+ my @ccs = @{$bug->cc_users};
+
+ # Include the people passed in as being in particular roles.
+ # This can include people who used to hold those roles.
+ # At this point, we don't care if there are duplicates in these arrays.
+ my $changer = $forced->{'changer'};
+ if ($forced->{'owner'}) {
+ push(@assignees, Bugzilla::User->check($forced->{'owner'}));
+ }
+
+ if ($forced->{'qacontact'}) {
+ push(@qa_contacts, Bugzilla::User->check($forced->{'qacontact'}));
+ }
+
+ if ($forced->{'cc'}) {
+ foreach my $cc (@{$forced->{'cc'}}) {
+ push(@ccs, Bugzilla::User->check($cc));
}
- else {
- my ($diffs, $referenced) = _get_diffs($bug, $end, \%user_cache);
- push(@diffs, @$diffs);
- push(@referenced_bugs, @$referenced);
+ }
+ my %user_cache = map { $_->id => $_ } (@assignees, @qa_contacts, @ccs);
+
+ my @diffs;
+ my @referenced_bugs;
+ if (!$start) {
+ @diffs = _get_new_bugmail_fields($bug);
+ }
+
+ if ($params->{dep_only}) {
+ my $fields = Bugzilla->fields({by_name => 1});
+ push(
+ @diffs,
+ {
+ field_name => 'bug_status',
+ field_desc => $fields->{bug_status}->description,
+ old => $params->{changes}->{bug_status}->[0],
+ new => $params->{changes}->{bug_status}->[1],
+ login_name => $changer->login,
+ blocker => $params->{blocker}
+ },
+ {
+ field_name => 'resolution',
+ field_desc => $fields->{resolution}->description,
+ old => $params->{changes}->{resolution}->[0],
+ new => $params->{changes}->{resolution}->[1],
+ login_name => $changer->login,
+ blocker => $params->{blocker}
+ }
+ );
+ push(@referenced_bugs, $params->{blocker}->id);
+ }
+ else {
+ my ($diffs, $referenced) = _get_diffs($bug, $end, \%user_cache);
+ push(@diffs, @$diffs);
+ push(@referenced_bugs, @$referenced);
+ }
+
+ my $comments = $bug->comments({after => $start, to => $end});
+
+ # Skip empty comments.
+ @$comments = grep { $_->type || $_->body =~ /\S/ } @$comments;
+
+ # Add duplicate bug to referenced bug list
+ foreach my $comment (@$comments) {
+ if ($comment->type == CMT_DUPE_OF || $comment->type == CMT_HAS_DUPE) {
+ push(@referenced_bugs, $comment->extra_data);
}
+ }
- my $comments = $bug->comments({ after => $start, to => $end });
- # Skip empty comments.
- @$comments = grep { $_->type || $_->body =~ /\S/ } @$comments;
+ # Add dependencies to referenced bug list on new bugs
+ if (!$start) {
+ push @referenced_bugs, @{$bug->dependson};
+ push @referenced_bugs, @{$bug->blocked};
+ }
- # Add duplicate bug to referenced bug list
- foreach my $comment (@$comments) {
- if ($comment->type == CMT_DUPE_OF || $comment->type == CMT_HAS_DUPE) {
- push(@referenced_bugs, $comment->extra_data);
- }
- }
+ # If no changes have been made, there is no need to process further.
+ return {'sent' => []} unless scalar(@diffs) || scalar(@$comments);
- # Add dependencies to referenced bug list on new bugs
- if (!$start) {
- push @referenced_bugs, @{ $bug->dependson };
- push @referenced_bugs, @{ $bug->blocked };
- }
+ ###########################################################################
+ # Start of email filtering code
+ ###########################################################################
- # If no changes have been made, there is no need to process further.
- return {'sent' => []} unless scalar(@diffs) || scalar(@$comments);
+ # A user_id => roles hash to keep track of people.
+ my %recipients;
+ my %watching;
- ###########################################################################
- # Start of email filtering code
- ###########################################################################
+ # Now we work out all the people involved with this bug, and note all of
+ # the relationships in a hash. The keys are userids, the values are an
+ # array of role constants.
- # A user_id => roles hash to keep track of people.
- my %recipients;
- my %watching;
+ # CCs
+ $recipients{$_->id}->{+REL_CC} = BIT_DIRECT foreach (@ccs);
- # Now we work out all the people involved with this bug, and note all of
- # the relationships in a hash. The keys are userids, the values are an
- # array of role constants.
+ # Reporter (there's only ever one)
+ $recipients{$bug->reporter->id}->{+REL_REPORTER} = BIT_DIRECT;
- # CCs
- $recipients{$_->id}->{+REL_CC} = BIT_DIRECT foreach (@ccs);
+ # QA Contact
+ if (Bugzilla->params->{'useqacontact'}) {
+ foreach (@qa_contacts) {
- # Reporter (there's only ever one)
- $recipients{$bug->reporter->id}->{+REL_REPORTER} = BIT_DIRECT;
-
- # QA Contact
- if (Bugzilla->params->{'useqacontact'}) {
- foreach (@qa_contacts) {
- # QA Contact can be blank; ignore it if so.
- $recipients{$_->id}->{+REL_QA} = BIT_DIRECT if $_;
- }
+ # QA Contact can be blank; ignore it if so.
+ $recipients{$_->id}->{+REL_QA} = BIT_DIRECT if $_;
}
-
- # Assignee
- $recipients{$_->id}->{+REL_ASSIGNEE} = BIT_DIRECT foreach (@assignees);
-
- # The last relevant set of people are those who are being removed from
- # their roles in this change. We get their names out of the diffs.
- foreach my $change (@diffs) {
- if ($change->{old}) {
- # You can't stop being the reporter, so we don't check that
- # relationship here.
- # Ignore people whose user account has been deleted or renamed.
- if ($change->{field_name} eq 'cc') {
- foreach my $cc_user (split(/[\s,]+/, $change->{old})) {
- my $uid = login_to_id($cc_user);
- $recipients{$uid}->{+REL_CC} = BIT_DIRECT if $uid;
- }
- }
- elsif ($change->{field_name} eq 'qa_contact') {
- my $uid = login_to_id($change->{old});
- $recipients{$uid}->{+REL_QA} = BIT_DIRECT if $uid;
- }
- elsif ($change->{field_name} eq 'assigned_to') {
- my $uid = login_to_id($change->{old});
- $recipients{$uid}->{+REL_ASSIGNEE} = BIT_DIRECT if $uid;
- }
+ }
+
+ # Assignee
+ $recipients{$_->id}->{+REL_ASSIGNEE} = BIT_DIRECT foreach (@assignees);
+
+ # The last relevant set of people are those who are being removed from
+ # their roles in this change. We get their names out of the diffs.
+ foreach my $change (@diffs) {
+ if ($change->{old}) {
+
+ # You can't stop being the reporter, so we don't check that
+ # relationship here.
+ # Ignore people whose user account has been deleted or renamed.
+ if ($change->{field_name} eq 'cc') {
+ foreach my $cc_user (split(/[\s,]+/, $change->{old})) {
+ my $uid = login_to_id($cc_user);
+ $recipients{$uid}->{+REL_CC} = BIT_DIRECT if $uid;
}
+ }
+ elsif ($change->{field_name} eq 'qa_contact') {
+ my $uid = login_to_id($change->{old});
+ $recipients{$uid}->{+REL_QA} = BIT_DIRECT if $uid;
+ }
+ elsif ($change->{field_name} eq 'assigned_to') {
+ my $uid = login_to_id($change->{old});
+ $recipients{$uid}->{+REL_ASSIGNEE} = BIT_DIRECT if $uid;
+ }
}
-
- # Make sure %user_cache has every user in it so far referenced
- foreach my $user_id (keys %recipients) {
- $user_cache{$user_id} ||= new Bugzilla::User({ id => $user_id, cache => 1 });
+ }
+
+ # Make sure %user_cache has every user in it so far referenced
+ foreach my $user_id (keys %recipients) {
+ $user_cache{$user_id} ||= new Bugzilla::User({id => $user_id, cache => 1});
+ }
+
+ Bugzilla::Hook::process(
+ 'bugmail_recipients',
+ {
+ bug => $bug,
+ recipients => \%recipients,
+ users => \%user_cache,
+ diffs => \@diffs
}
+ );
- Bugzilla::Hook::process('bugmail_recipients',
- { bug => $bug, recipients => \%recipients,
- users => \%user_cache, diffs => \@diffs });
-
- if (scalar keys %recipients) {
- # Find all those user-watching anyone on the current list, who is not
- # on it already themselves.
- my $involved = join(",", keys %recipients);
-
- my $userwatchers =
- $dbh->selectall_arrayref("SELECT watcher, watched FROM watch
- WHERE watched IN ($involved)");
-
- # Mark these people as having the role of the person they are watching
- foreach my $watch (@$userwatchers) {
- while (my ($role, $bits) = each %{$recipients{$watch->[1]}}) {
- $recipients{$watch->[0]}->{$role} |= BIT_WATCHING
- if $bits & BIT_DIRECT;
- }
- push(@{$watching{$watch->[0]}}, $watch->[1]);
- }
- }
+ if (scalar keys %recipients) {
- # Global watcher
- my @watchers = split(/\s*,\s*/ms, Bugzilla->params->{'globalwatchers'});
- foreach (@watchers) {
- my $watcher_id = login_to_id($_);
- next unless $watcher_id;
- $recipients{$watcher_id}->{+REL_GLOBAL_WATCHER} = BIT_DIRECT;
- }
-
- # We now have a complete set of all the users, and their relationships to
- # the bug in question. However, we are not necessarily going to mail them
- # all - there are preferences, permissions checks and all sorts to do yet.
- my @sent;
-
- # The email client will display the Date: header in the desired timezone,
- # so we can always use UTC here.
- my $date = $params->{dep_only} ? $end : $bug->delta_ts;
- $date = format_time($date, '%a, %d %b %Y %T %z', 'UTC');
-
- # Remove duplicate references, and convert to bug objects
- @referenced_bugs = @{ Bugzilla::Bug->new_from_list([uniq @referenced_bugs]) };
-
- foreach my $user_id (keys %recipients) {
- my %rels_which_want;
- my $user = $user_cache{$user_id} ||= new Bugzilla::User({ id => $user_id, cache => 1 });
- # Deleted users must be excluded.
- next unless $user;
-
- # If email notifications are disabled for this account, or the bug
- # is ignored, there is no need to do additional checks.
- next if ($user->email_disabled || $user->is_bug_ignored($id));
-
- if ($user->can_see_bug($id)) {
- # Go through each role the user has and see if they want mail in
- # that role.
- foreach my $relationship (keys %{$recipients{$user_id}}) {
- if ($user->wants_bug_mail($bug,
- $relationship,
- $start ? \@diffs : [],
- $comments,
- $params->{dep_only},
- $changer))
- {
- $rels_which_want{$relationship} =
- $recipients{$user_id}->{$relationship};
- }
- }
- }
+ # Find all those user-watching anyone on the current list, who is not
+ # on it already themselves.
+ my $involved = join(",", keys %recipients);
- if (scalar(%rels_which_want)) {
- # So the user exists, can see the bug, and wants mail in at least
- # one role. But do we want to send it to them?
-
- # We shouldn't send mail if this is a dependency mail and the
- # depending bug is not visible to the user.
- # This is to avoid leaking the summary of a confidential bug.
- my $dep_ok = 1;
- if ($params->{dep_only}) {
- $dep_ok = $user->can_see_bug($params->{blocker}->id) ? 1 : 0;
- }
-
- # Make sure the user isn't in the nomail list, and the dep check passed.
- # BMO: never send emails to bugs or .tld addresses. this check needs to
- # happen after the bugmail_recipients hook.
- if ($user->email_enabled && $dep_ok &&
- ($user->login !~ /\.(?:bugs|tld)$/))
- {
- # Don't show summaries for bugs the user can't access, and
- # provide a hook for extensions such as SecureMail to filter
- # this list.
- #
- # We build an array with the short_desc as a separate item to
- # allow extensions to modify the summary without touching the
- # bug object.
- my $referenced_bugs = [];
- foreach my $ref (@{ $user->visible_bugs(\@referenced_bugs) }) {
- push @$referenced_bugs, {
- bug => $ref,
- id => $ref->id,
- short_desc => $ref->short_desc,
- };
- }
- Bugzilla::Hook::process('bugmail_referenced_bugs',
- { updated_bug => $bug,
- referenced_bugs => $referenced_bugs });
-
- my $sent_mail = sendMail(
- { to => $user,
- bug => $bug,
- comments => $comments,
- date => $date,
- changer => $changer,
- watchers => exists $watching{$user_id} ?
- $watching{$user_id} : undef,
- diffs => \@diffs,
- rels_which_want => \%rels_which_want,
- referenced_bugs => $referenced_bugs,
- dep_only => $params->{dep_only}
- });
- push(@sent, $user->login) if $sent_mail;
- }
- }
- }
+ my $userwatchers = $dbh->selectall_arrayref(
+ "SELECT watcher, watched FROM watch
+ WHERE watched IN ($involved)"
+ );
- # When sending bugmail about a blocker being reopened or resolved,
- # we say nothing about changes in the bug being blocked, so we must
- # not update lastdiffed in this case.
- if (!$params->{dep_only}) {
- $dbh->do('UPDATE bugs SET lastdiffed = ? WHERE bug_id = ?',
- undef, ($end, $id));
- $bug->{lastdiffed} = $end;
+ # Mark these people as having the role of the person they are watching
+ foreach my $watch (@$userwatchers) {
+ while (my ($role, $bits) = each %{$recipients{$watch->[1]}}) {
+ $recipients{$watch->[0]}->{$role} |= BIT_WATCHING if $bits & BIT_DIRECT;
+ }
+ push(@{$watching{$watch->[0]}}, $watch->[1]);
}
-
- return {'sent' => \@sent};
-}
-
-sub sendMail {
- my $params = shift;
-
- my $user = $params->{to};
- my $bug = $params->{bug};
- my @send_comments = @{ $params->{comments} };
- my $date = $params->{date};
- my $changer = $params->{changer};
- my $watchingRef = $params->{watchers};
- my @diffs = @{ $params->{diffs} };
- my $relRef = $params->{rels_which_want};
- my $referenced_bugs = $params->{referenced_bugs};
- my $dep_only = $params->{dep_only};
- my $attach_id;
-
- # Only display changes the user is allowed see.
- my @display_diffs;
-
- foreach my $diff (@diffs) {
- my $add_diff = 0;
-
- if (grep { $_ eq $diff->{field_name} } TIMETRACKING_FIELDS) {
- $add_diff = 1 if $user->is_timetracker;
- }
- elsif (!$diff->{isprivate} || $user->is_insider) {
- $add_diff = 1;
+ }
+
+ # Global watcher
+ my @watchers = split(/\s*,\s*/ms, Bugzilla->params->{'globalwatchers'});
+ foreach (@watchers) {
+ my $watcher_id = login_to_id($_);
+ next unless $watcher_id;
+ $recipients{$watcher_id}->{+REL_GLOBAL_WATCHER} = BIT_DIRECT;
+ }
+
+ # We now have a complete set of all the users, and their relationships to
+ # the bug in question. However, we are not necessarily going to mail them
+ # all - there are preferences, permissions checks and all sorts to do yet.
+ my @sent;
+
+ # The email client will display the Date: header in the desired timezone,
+ # so we can always use UTC here.
+ my $date = $params->{dep_only} ? $end : $bug->delta_ts;
+ $date = format_time($date, '%a, %d %b %Y %T %z', 'UTC');
+
+ # Remove duplicate references, and convert to bug objects
+ @referenced_bugs = @{Bugzilla::Bug->new_from_list([uniq @referenced_bugs])};
+
+ foreach my $user_id (keys %recipients) {
+ my %rels_which_want;
+ my $user = $user_cache{$user_id}
+ ||= new Bugzilla::User({id => $user_id, cache => 1});
+
+ # Deleted users must be excluded.
+ next unless $user;
+
+ # If email notifications are disabled for this account, or the bug
+ # is ignored, there is no need to do additional checks.
+ next if ($user->email_disabled || $user->is_bug_ignored($id));
+
+ if ($user->can_see_bug($id)) {
+
+ # Go through each role the user has and see if they want mail in
+ # that role.
+ foreach my $relationship (keys %{$recipients{$user_id}}) {
+ if ($user->wants_bug_mail(
+ $bug, $relationship, $start ? \@diffs : [],
+ $comments, $params->{dep_only}, $changer
+ ))
+ {
+ $rels_which_want{$relationship} = $recipients{$user_id}->{$relationship};
}
- push(@display_diffs, $diff) if $add_diff;
- $attach_id = $diff->{attach_id} if $diff->{attach_id};
+ }
}
- if (!$user->is_insider) {
- @send_comments = grep { !$_->is_private } @send_comments;
- }
-
- if (!scalar(@display_diffs) && !scalar(@send_comments)) {
- # Whoops, no differences!
- return 0;
+ if (scalar(%rels_which_want)) {
+
+ # So the user exists, can see the bug, and wants mail in at least
+ # one role. But do we want to send it to them?
+
+ # We shouldn't send mail if this is a dependency mail and the
+ # depending bug is not visible to the user.
+ # This is to avoid leaking the summary of a confidential bug.
+ my $dep_ok = 1;
+ if ($params->{dep_only}) {
+ $dep_ok = $user->can_see_bug($params->{blocker}->id) ? 1 : 0;
+ }
+
+ # Make sure the user isn't in the nomail list, and the dep check passed.
+ # BMO: never send emails to bugs or .tld addresses. this check needs to
+ # happen after the bugmail_recipients hook.
+ if ($user->email_enabled && $dep_ok && ($user->login !~ /\.(?:bugs|tld)$/)) {
+
+ # Don't show summaries for bugs the user can't access, and
+ # provide a hook for extensions such as SecureMail to filter
+ # this list.
+ #
+ # We build an array with the short_desc as a separate item to
+ # allow extensions to modify the summary without touching the
+ # bug object.
+ my $referenced_bugs = [];
+ foreach my $ref (@{$user->visible_bugs(\@referenced_bugs)}) {
+ push @$referenced_bugs,
+ {bug => $ref, id => $ref->id, short_desc => $ref->short_desc,};
+ }
+ Bugzilla::Hook::process('bugmail_referenced_bugs',
+ {updated_bug => $bug, referenced_bugs => $referenced_bugs});
+
+ my $sent_mail = sendMail({
+ to => $user,
+ bug => $bug,
+ comments => $comments,
+ date => $date,
+ changer => $changer,
+ watchers => exists $watching{$user_id} ? $watching{$user_id} : undef,
+ diffs => \@diffs,
+ rels_which_want => \%rels_which_want,
+ referenced_bugs => $referenced_bugs,
+ dep_only => $params->{dep_only}
+ });
+ push(@sent, $user->login) if $sent_mail;
+ }
}
+ }
- my (@reasons, @reasons_watch);
- while (my ($relationship, $bits) = each %{$relRef}) {
- push(@reasons, $relationship) if ($bits & BIT_DIRECT);
- push(@reasons_watch, $relationship) if ($bits & BIT_WATCHING);
- }
+ # When sending bugmail about a blocker being reopened or resolved,
+ # we say nothing about changes in the bug being blocked, so we must
+ # not update lastdiffed in this case.
+ if (!$params->{dep_only}) {
+ $dbh->do('UPDATE bugs SET lastdiffed = ? WHERE bug_id = ?', undef, ($end, $id));
+ $bug->{lastdiffed} = $end;
+ }
- my %relationships = relationships();
- my @headerrel = map { $relationships{$_} } @reasons;
- my @watchingrel = map { $relationships{$_} } @reasons_watch;
- push(@headerrel, 'None') unless @headerrel;
- push(@watchingrel, 'None') unless @watchingrel;
- push @watchingrel, map { user_id_to_login($_) } @$watchingRef;
-
- # BMO: Use field descriptions instead of field names in header
- my @changedfields = uniq map { $_->{field_desc} } @display_diffs;
- my @changedfieldnames = uniq map { $_->{field_name} } @display_diffs;
-
- # BMO: Add a field to indicate when a comment was added
- if (grep($_->type != CMT_ATTACHMENT_CREATED, @send_comments)) {
- push(@changedfields, 'Comment Created');
- push(@changedfieldnames, 'comment');
- }
+ return {'sent' => \@sent};
+}
- # Add attachments.created to changedfields if one or more
- # comments contain information about a new attachment
- if (grep($_->type == CMT_ATTACHMENT_CREATED, @send_comments)) {
- push(@changedfields, 'Attachment Created');
- push(@changedfieldnames, 'attachment.created');
+sub sendMail {
+ my $params = shift;
+
+ my $user = $params->{to};
+ my $bug = $params->{bug};
+ my @send_comments = @{$params->{comments}};
+ my $date = $params->{date};
+ my $changer = $params->{changer};
+ my $watchingRef = $params->{watchers};
+ my @diffs = @{$params->{diffs}};
+ my $relRef = $params->{rels_which_want};
+ my $referenced_bugs = $params->{referenced_bugs};
+ my $dep_only = $params->{dep_only};
+ my $attach_id;
+
+ # Only display changes the user is allowed see.
+ my @display_diffs;
+
+ foreach my $diff (@diffs) {
+ my $add_diff = 0;
+
+ if (grep { $_ eq $diff->{field_name} } TIMETRACKING_FIELDS) {
+ $add_diff = 1 if $user->is_timetracker;
}
-
- my $bugmailtype = "changed";
- $bugmailtype = "new" if !$bug->lastdiffed;
- $bugmailtype = "dep_changed" if $dep_only;
-
- my $vars = {
- date => $date,
- to_user => $user,
- bug => $bug,
- attach_id => $attach_id,
- reasons => \@reasons,
- reasons_watch => \@reasons_watch,
- reasonsheader => join(" ", @headerrel),
- reasonswatchheader => join(" ", @watchingrel),
- changer => $changer,
- diffs => \@display_diffs,
- changedfields => \@changedfields,
- changedfieldnames => \@changedfieldnames,
- new_comments => \@send_comments,
- threadingmarker => build_thread_marker($bug->id, $user->id, !$bug->lastdiffed),
- referenced_bugs => $referenced_bugs,
- bugmailtype => $bugmailtype,
- };
-
- if (Bugzilla->get_param_with_override('use_mailer_queue')) {
- enqueue($vars);
- } else {
- MessageToMTA(_generate_bugmail($vars));
+ elsif (!$diff->{isprivate} || $user->is_insider) {
+ $add_diff = 1;
}
-
- return 1;
+ push(@display_diffs, $diff) if $add_diff;
+ $attach_id = $diff->{attach_id} if $diff->{attach_id};
+ }
+
+ if (!$user->is_insider) {
+ @send_comments = grep { !$_->is_private } @send_comments;
+ }
+
+ if (!scalar(@display_diffs) && !scalar(@send_comments)) {
+
+ # Whoops, no differences!
+ return 0;
+ }
+
+ my (@reasons, @reasons_watch);
+ while (my ($relationship, $bits) = each %{$relRef}) {
+ push(@reasons, $relationship) if ($bits & BIT_DIRECT);
+ push(@reasons_watch, $relationship) if ($bits & BIT_WATCHING);
+ }
+
+ my %relationships = relationships();
+ my @headerrel = map { $relationships{$_} } @reasons;
+ my @watchingrel = map { $relationships{$_} } @reasons_watch;
+ push(@headerrel, 'None') unless @headerrel;
+ push(@watchingrel, 'None') unless @watchingrel;
+ push @watchingrel, map { user_id_to_login($_) } @$watchingRef;
+
+ # BMO: Use field descriptions instead of field names in header
+ my @changedfields = uniq map { $_->{field_desc} } @display_diffs;
+ my @changedfieldnames = uniq map { $_->{field_name} } @display_diffs;
+
+ # BMO: Add a field to indicate when a comment was added
+ if (grep($_->type != CMT_ATTACHMENT_CREATED, @send_comments)) {
+ push(@changedfields, 'Comment Created');
+ push(@changedfieldnames, 'comment');
+ }
+
+ # Add attachments.created to changedfields if one or more
+ # comments contain information about a new attachment
+ if (grep($_->type == CMT_ATTACHMENT_CREATED, @send_comments)) {
+ push(@changedfields, 'Attachment Created');
+ push(@changedfieldnames, 'attachment.created');
+ }
+
+ my $bugmailtype = "changed";
+ $bugmailtype = "new" if !$bug->lastdiffed;
+ $bugmailtype = "dep_changed" if $dep_only;
+
+ my $vars = {
+ date => $date,
+ to_user => $user,
+ bug => $bug,
+ attach_id => $attach_id,
+ reasons => \@reasons,
+ reasons_watch => \@reasons_watch,
+ reasonsheader => join(" ", @headerrel),
+ reasonswatchheader => join(" ", @watchingrel),
+ changer => $changer,
+ diffs => \@display_diffs,
+ changedfields => \@changedfields,
+ changedfieldnames => \@changedfieldnames,
+ new_comments => \@send_comments,
+ threadingmarker => build_thread_marker($bug->id, $user->id, !$bug->lastdiffed),
+ referenced_bugs => $referenced_bugs,
+ bugmailtype => $bugmailtype,
+ };
+
+ if (Bugzilla->get_param_with_override('use_mailer_queue')) {
+ enqueue($vars);
+ }
+ else {
+ MessageToMTA(_generate_bugmail($vars));
+ }
+
+ return 1;
}
sub enqueue {
- my ($vars) = @_;
-
- # BMO: allow modification of the email at the time it was generated
- Bugzilla::Hook::process('bugmail_enqueue', { vars => $vars });
-
- # we need to flatten all objects to a hash before pushing to the job queue.
- # the hashes need to be inflated in the dequeue method.
- $vars->{bug} = _flatten_object($vars->{bug});
- $vars->{to_user} = _flatten_object($vars->{to_user});
- $vars->{changer} = _flatten_object($vars->{changer});
- $vars->{new_comments} = [ map { _flatten_object($_) } @{ $vars->{new_comments} } ];
- foreach my $diff (@{ $vars->{diffs} }) {
- $diff->{who} = _flatten_object($diff->{who});
- if (exists $diff->{blocker}) {
- $diff->{blocker} = _flatten_object($diff->{blocker});
- }
- }
- foreach my $reference (@{ $vars->{referenced_bugs} }) {
- $reference->{bug} = _flatten_object($reference->{bug});
+ my ($vars) = @_;
+
+ # BMO: allow modification of the email at the time it was generated
+ Bugzilla::Hook::process('bugmail_enqueue', {vars => $vars});
+
+ # we need to flatten all objects to a hash before pushing to the job queue.
+ # the hashes need to be inflated in the dequeue method.
+ $vars->{bug} = _flatten_object($vars->{bug});
+ $vars->{to_user} = _flatten_object($vars->{to_user});
+ $vars->{changer} = _flatten_object($vars->{changer});
+ $vars->{new_comments} = [map { _flatten_object($_) } @{$vars->{new_comments}}];
+ foreach my $diff (@{$vars->{diffs}}) {
+ $diff->{who} = _flatten_object($diff->{who});
+ if (exists $diff->{blocker}) {
+ $diff->{blocker} = _flatten_object($diff->{blocker});
}
- Bugzilla->job_queue->insert('bug_mail', { vars => $vars });
+ }
+ foreach my $reference (@{$vars->{referenced_bugs}}) {
+ $reference->{bug} = _flatten_object($reference->{bug});
+ }
+ Bugzilla->job_queue->insert('bug_mail', {vars => $vars});
}
sub dequeue {
- my ($payload) = @_;
- # clone the payload so we can modify it without impacting TheSchwartz's
- # ability to process the job when we've finished
- my $vars = dclone($payload);
- # inflate objects
- $vars->{bug} = Bugzilla::Bug->new_from_hash($vars->{bug});
- $vars->{to_user} = Bugzilla::User->new_from_hash($vars->{to_user});
- $vars->{changer} = Bugzilla::User->new_from_hash($vars->{changer});
- $vars->{new_comments} = [ map { Bugzilla::Comment->new_from_hash($_) } @{ $vars->{new_comments} } ];
- foreach my $diff (@{ $vars->{diffs} }) {
- $diff->{who} = Bugzilla::User->new_from_hash($diff->{who});
- if (exists $diff->{blocker}) {
- $diff->{blocker} = Bugzilla::Bug->new_from_hash($diff->{blocker});
- }
+ my ($payload) = @_;
+
+ # clone the payload so we can modify it without impacting TheSchwartz's
+ # ability to process the job when we've finished
+ my $vars = dclone($payload);
+
+ # inflate objects
+ $vars->{bug} = Bugzilla::Bug->new_from_hash($vars->{bug});
+ $vars->{to_user} = Bugzilla::User->new_from_hash($vars->{to_user});
+ $vars->{changer} = Bugzilla::User->new_from_hash($vars->{changer});
+ $vars->{new_comments}
+ = [map { Bugzilla::Comment->new_from_hash($_) } @{$vars->{new_comments}}];
+ foreach my $diff (@{$vars->{diffs}}) {
+ $diff->{who} = Bugzilla::User->new_from_hash($diff->{who});
+ if (exists $diff->{blocker}) {
+ $diff->{blocker} = Bugzilla::Bug->new_from_hash($diff->{blocker});
}
- # generate bugmail and send
- MessageToMTA(_generate_bugmail($vars), 1);
-}
+ }
-sub _flatten_object {
- my ($object) = @_;
- # nothing to do if it's already flattened
- return $object unless blessed($object);
- # the same objects are used for each recipient, so cache the flattened hash
- my $cache = Bugzilla->request_cache->{bugmail_flat_objects} ||= {};
- my $key = blessed($object) . '-' . $object->id;
- return $cache->{$key} ||= $object->flatten_to_hash;
+ # generate bugmail and send
+ MessageToMTA(_generate_bugmail($vars), 1);
}
-sub _generate_bugmail {
- my ($vars) = @_;
- my $user = $vars->{to_user};
- my $template = Bugzilla->template_inner($user->setting('lang'));
- my ($msg_text, $msg_html, $msg_header);
-
- $template->process("email/bugmail-header.txt.tmpl", $vars, \$msg_header)
- || ThrowTemplateError($template->error());
-
- $template->process("email/bugmail.txt.tmpl", $vars, \$msg_text)
- || ThrowTemplateError($template->error());
-
- my @parts = (
- Email::MIME->create(
- attributes => {
- content_type => "text/plain",
- },
- body => $msg_text,
- )
- );
- if ($user->setting('email_format') eq 'html') {
- $template->process("email/bugmail.html.tmpl", $vars, \$msg_html)
- || ThrowTemplateError($template->error());
- push @parts, Email::MIME->create(
- attributes => {
- content_type => "text/html",
- },
- body => $msg_html,
- );
- }
-
- # TT trims the trailing newline, and threadingmarker may be ignored.
- my $email = new Email::MIME("$msg_header\n");
-
- # For tracking/diagnostic purposes, add our hostname
- $email->header_set('X-Generated-By' => hostname());
+sub _flatten_object {
+ my ($object) = @_;
- if (scalar(@parts) == 1) {
- $email->content_type_set($parts[0]->content_type);
- } else {
- $email->content_type_set('multipart/alternative');
- }
- $email->parts_set(\@parts);
+ # nothing to do if it's already flattened
+ return $object unless blessed($object);
- # BMO: allow modification of the email given the enqueued variables
- Bugzilla::Hook::process('bugmail_generate', { vars => $vars, email => $email });
+ # the same objects are used for each recipient, so cache the flattened hash
+ my $cache = Bugzilla->request_cache->{bugmail_flat_objects} ||= {};
+ my $key = blessed($object) . '-' . $object->id;
+ return $cache->{$key} ||= $object->flatten_to_hash;
+}
- return $email;
+sub _generate_bugmail {
+ my ($vars) = @_;
+ my $user = $vars->{to_user};
+ my $template = Bugzilla->template_inner($user->setting('lang'));
+ my ($msg_text, $msg_html, $msg_header);
+
+ $template->process("email/bugmail-header.txt.tmpl", $vars, \$msg_header)
+ || ThrowTemplateError($template->error());
+
+ $template->process("email/bugmail.txt.tmpl", $vars, \$msg_text)
+ || ThrowTemplateError($template->error());
+
+ my @parts = (Email::MIME->create(
+ attributes => {content_type => "text/plain",},
+ body => $msg_text,
+ ));
+ if ($user->setting('email_format') eq 'html') {
+ $template->process("email/bugmail.html.tmpl", $vars, \$msg_html)
+ || ThrowTemplateError($template->error());
+ push @parts,
+ Email::MIME->create(
+ attributes => {content_type => "text/html",},
+ body => $msg_html,
+ );
+ }
+
+ # TT trims the trailing newline, and threadingmarker may be ignored.
+ my $email = new Email::MIME("$msg_header\n");
+
+ # For tracking/diagnostic purposes, add our hostname
+ $email->header_set('X-Generated-By' => hostname());
+
+ if (scalar(@parts) == 1) {
+ $email->content_type_set($parts[0]->content_type);
+ }
+ else {
+ $email->content_type_set('multipart/alternative');
+ }
+ $email->parts_set(\@parts);
+
+ # BMO: allow modification of the email given the enqueued variables
+ Bugzilla::Hook::process('bugmail_generate', {vars => $vars, email => $email});
+
+ return $email;
}
sub _get_diffs {
- my ($bug, $end, $user_cache) = @_;
- my $dbh = Bugzilla->dbh;
-
- my @args = ($bug->id);
- # If lastdiffed is NULL, then we don't limit the search on time.
- my $when_restriction = '';
- if ($bug->lastdiffed) {
- $when_restriction = ' AND bug_when > ? AND bug_when <= ?';
- push @args, ($bug->lastdiffed, $end);
- }
+ my ($bug, $end, $user_cache) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my @args = ($bug->id);
- my $diffs = $dbh->selectall_arrayref(
- "SELECT fielddefs.name AS field_name,
+ # If lastdiffed is NULL, then we don't limit the search on time.
+ my $when_restriction = '';
+ if ($bug->lastdiffed) {
+ $when_restriction = ' AND bug_when > ? AND bug_when <= ?';
+ push @args, ($bug->lastdiffed, $end);
+ }
+
+ my $diffs = $dbh->selectall_arrayref(
+ "SELECT fielddefs.name AS field_name,
fielddefs.description AS field_desc,
bugs_activity.bug_when, bugs_activity.removed AS old,
bugs_activity.added AS new, bugs_activity.attach_id,
@@ -557,90 +575,94 @@ sub _get_diffs {
ON fielddefs.id = bugs_activity.fieldid
WHERE bugs_activity.bug_id = ?
$when_restriction
- ORDER BY bugs_activity.bug_when, fielddefs.description", {Slice=>{}}, @args);
- my $referenced_bugs = [];
-
- foreach my $diff (@$diffs) {
- $user_cache->{$diff->{who}} ||= new Bugzilla::User({ id => $diff->{who}, cache => 1 });
- $diff->{who} = $user_cache->{$diff->{who}};
- if ($diff->{attach_id}) {
- $diff->{isprivate} = $dbh->selectrow_array(
- 'SELECT isprivate FROM attachments WHERE attach_id = ?',
- undef, $diff->{attach_id});
- }
- if ($diff->{field_name} eq 'longdescs.isprivate') {
- my $comment = Bugzilla::Comment->new($diff->{comment_id});
- $diff->{num} = $comment->count;
- $diff->{isprivate} = $diff->{new};
- }
- elsif ($diff->{field_name} eq 'dependson' || $diff->{field_name} eq 'blocked') {
- push @$referenced_bugs, grep { /^\d+$/ } split(/[\s,]+/, $diff->{old});
- push @$referenced_bugs, grep { /^\d+$/ } split(/[\s,]+/, $diff->{new});
- }
+ ORDER BY bugs_activity.bug_when, fielddefs.description", {Slice => {}},
+ @args
+ );
+ my $referenced_bugs = [];
+
+ foreach my $diff (@$diffs) {
+ $user_cache->{$diff->{who}}
+ ||= new Bugzilla::User({id => $diff->{who}, cache => 1});
+ $diff->{who} = $user_cache->{$diff->{who}};
+ if ($diff->{attach_id}) {
+ $diff->{isprivate}
+ = $dbh->selectrow_array(
+ 'SELECT isprivate FROM attachments WHERE attach_id = ?',
+ undef, $diff->{attach_id});
+ }
+ if ($diff->{field_name} eq 'longdescs.isprivate') {
+ my $comment = Bugzilla::Comment->new($diff->{comment_id});
+ $diff->{num} = $comment->count;
+ $diff->{isprivate} = $diff->{new};
}
+ elsif ($diff->{field_name} eq 'dependson' || $diff->{field_name} eq 'blocked') {
+ push @$referenced_bugs, grep {/^\d+$/} split(/[\s,]+/, $diff->{old});
+ push @$referenced_bugs, grep {/^\d+$/} split(/[\s,]+/, $diff->{new});
+ }
+ }
- return ($diffs, $referenced_bugs);
+ return ($diffs, $referenced_bugs);
}
sub _get_new_bugmail_fields {
- my $bug = shift;
- my @fields = @{ Bugzilla->fields({obsolete => 0, in_new_bugmail => 1}) };
- my @diffs;
-
- # Show fields in the same order as the DEFAULT_FIELDS list, which mirrors
- # 4.0's behavour and provides sane grouping of similar fields.
- # Any additional fields are sorted by descrsiption
- my @prepend;
- foreach my $name (map { $_->{name} } Bugzilla::Field::DEFAULT_FIELDS) {
- my $idx = firstidx { $_->name eq $name } @fields;
- if ($idx != -1) {
- push(@prepend, $fields[$idx]);
- splice(@fields, $idx, 1);
- }
+ my $bug = shift;
+ my @fields = @{Bugzilla->fields({obsolete => 0, in_new_bugmail => 1})};
+ my @diffs;
+
+ # Show fields in the same order as the DEFAULT_FIELDS list, which mirrors
+ # 4.0's behavour and provides sane grouping of similar fields.
+ # Any additional fields are sorted by descrsiption
+ my @prepend;
+ foreach my $name (map { $_->{name} } Bugzilla::Field::DEFAULT_FIELDS) {
+ my $idx = firstidx { $_->name eq $name } @fields;
+ if ($idx != -1) {
+ push(@prepend, $fields[$idx]);
+ splice(@fields, $idx, 1);
}
- @fields = sort { $a->description cmp $b->description } @fields;
- @fields = (@prepend, @fields);
-
- foreach my $field (@fields) {
- my $name = $field->name;
- my $value = $bug->$name;
-
- if (ref $value eq 'ARRAY') {
- my @new_values;
- foreach my $item (@$value) {
- if (blessed($item) && $item->isa('Bugzilla::User')) {
- push(@new_values, $item->login);
- }
- else {
- push(@new_values, $item);
- }
- }
- $value = join(', ', @new_values);
- }
- elsif (blessed($value) && $value->isa('Bugzilla::User')) {
- $value = $value->login;
- }
- elsif (blessed($value) && $value->isa('Bugzilla::Object')) {
- $value = $value->name;
+ }
+ @fields = sort { $a->description cmp $b->description } @fields;
+ @fields = (@prepend, @fields);
+
+ foreach my $field (@fields) {
+ my $name = $field->name;
+ my $value = $bug->$name;
+
+ if (ref $value eq 'ARRAY') {
+ my @new_values;
+ foreach my $item (@$value) {
+ if (blessed($item) && $item->isa('Bugzilla::User')) {
+ push(@new_values, $item->login);
}
- elsif ($name eq 'estimated_time') {
- # "0.00" (which is what we get from the DB) is true,
- # so we explicitly do a numerical comparison with 0.
- $value = 0 if $value == 0;
+ else {
+ push(@new_values, $item);
}
- elsif ($name eq 'deadline') {
- $value = time2str("%Y-%m-%d", str2time($value)) if $value;
- }
-
- # If there isn't anything to show, don't include this header.
- next unless $value;
+ }
+ $value = join(', ', @new_values);
+ }
+ elsif (blessed($value) && $value->isa('Bugzilla::User')) {
+ $value = $value->login;
+ }
+ elsif (blessed($value) && $value->isa('Bugzilla::Object')) {
+ $value = $value->name;
+ }
+ elsif ($name eq 'estimated_time') {
- push(@diffs, {field_name => $name,
- field_desc => $field->description,
- new => $value});
+ # "0.00" (which is what we get from the DB) is true,
+ # so we explicitly do a numerical comparison with 0.
+ $value = 0 if $value == 0;
+ }
+ elsif ($name eq 'deadline') {
+ $value = time2str("%Y-%m-%d", str2time($value)) if $value;
}
- return @diffs;
+ # If there isn't anything to show, don't include this header.
+ next unless $value;
+
+ push(@diffs,
+ {field_name => $name, field_desc => $field->description, new => $value});
+ }
+
+ return @diffs;
}
1;