Skip to content

Commit 70b298e

Browse files
committed
Bug 1163868: Include requests from others in RequestNagger
1 parent a92ec27 commit 70b298e

10 files changed

+570
-326
lines changed

extensions/RequestNagger/bin/send-request-nags.pl

Lines changed: 180 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@
1919
use Bugzilla::Attachment;
2020
use Bugzilla::Bug;
2121
use Bugzilla::Constants;
22+
use Bugzilla::Hook;
2223
use Bugzilla::Error;
2324
use Bugzilla::Extension::RequestNagger::Constants;
2425
use Bugzilla::Extension::RequestNagger::Bug;
2526
use Bugzilla::Mailer;
2627
use Bugzilla::User;
2728
use Bugzilla::Util qw(format_time);
2829
use Email::MIME;
29-
use Sys::Hostname;
30+
use Sys::Hostname qw(hostname);
3031

3132
Bugzilla->usage_mode(USAGE_MODE_CMDLINE);
3233

@@ -42,168 +43,211 @@
4243

4344
# send nags to requestees
4445
send_nags(
45-
sql => REQUESTEE_NAG_SQL,
46-
template => 'requestee',
47-
recipient_field => 'requestee_id',
48-
date => $date,
46+
requestee_sql => REQUESTEE_NAG_SQL,
47+
setter_sql => SETTER_NAG_SQL,
48+
template => 'user',
49+
date => $date,
4950
);
5051

5152
# send nags to watchers
5253
send_nags(
53-
sql => WATCHING_NAG_SQL,
54-
template => 'watching',
55-
recipient_field => 'watcher_id',
56-
date => $date,
54+
requestee_sql => WATCHING_REQUESTEE_NAG_SQL,
55+
setter_sql => WATCHING_SETTER_NAG_SQL,
56+
template => 'watching',
57+
date => $date,
5758
);
5859

5960
sub send_nags {
6061
my (%args) = @_;
61-
my $rows = $dbh->selectall_arrayref($args{sql}, { Slice => {} });
62-
63-
# iterate over rows, sending email when the current recipient changes
64-
my $requests = [];
65-
my $current_recipient;
66-
foreach my $request (@$rows) {
67-
# send previous user's requests
68-
if (!$current_recipient || $request->{$args{recipient_field}} != $current_recipient->id) {
69-
send_email(%args, recipient => $current_recipient, requests => $requests);
70-
$current_recipient = Bugzilla::User->new({ id => $request->{$args{recipient_field}}, cache => 1 });
71-
$requests = [];
62+
63+
my @reports = qw( requestee setter );
64+
my $securemail = Bugzilla::User->can('public_key');
65+
my $requests = {};
66+
67+
# get requests
68+
69+
foreach my $report (@reports) {
70+
71+
# collate requests
72+
my $rows = $dbh->selectall_arrayref($args{$report . '_sql'}, { Slice => {} });
73+
foreach my $request (@$rows) {
74+
next unless _include_request($request, $report);
75+
76+
my $target = Bugzilla::User->new({ id => $request->{target_id}, cache => 1 });
77+
push @{
78+
$requests
79+
->{$request->{recipient_id}}
80+
->{$target->login}
81+
->{$report}
82+
->{$request->{flag_type}}
83+
}, $request;
84+
push @{
85+
$requests
86+
->{$request->{recipient_id}}
87+
->{$target->login}
88+
->{bug_ids}
89+
->{$report}
90+
}, $request->{bug_id};
7291
}
7392

74-
# check group membership
75-
$request->{requestee} = Bugzilla::User->new({ id => $request->{requestee_id}, cache => 1 });
76-
my $group;
77-
foreach my $type (FLAG_TYPES) {
78-
next unless $type->{type} eq $request->{flag_type};
79-
$group = $type->{group};
80-
last;
93+
# process requests here to avoid doing it in the templates
94+
foreach my $recipient_id (keys %$requests) {
95+
foreach my $target_login (keys %{ $requests->{$recipient_id} }) {
96+
my $rh = $requests->{$recipient_id}->{$target_login};
97+
98+
# build a list of valid types in the correct order
99+
$rh->{types}->{$report} = [];
100+
foreach my $type (map { $_->{type} } FLAG_TYPES) {
101+
next unless exists $rh->{$report}->{$type};
102+
push @{ $rh->{types}->{$report} }, $type;
103+
}
104+
105+
# build a summary
106+
$rh->{summary}->{$report} = join(', ',
107+
map { scalar(@{ $rh->{$report}->{$_} }) . ' ' . $_ }
108+
@{ $rh->{types}->{$report} }
109+
);
110+
}
81111
}
82-
next unless $request->{requestee}->in_group($group);
83-
84-
# check bug visibility
85-
next unless $current_recipient->can_see_bug($request->{bug_id});
86-
87-
# create objects
88-
$request->{bug} = Bugzilla::Bug->new({ id => $request->{bug_id}, cache => 1 });
89-
$request->{requester} = Bugzilla::User->new({ id => $request->{requester_id}, cache => 1 });
90-
$request->{flag} = Bugzilla::Flag->new({ id => $request->{flag_id}, cache => 1 });
91-
if ($request->{attach_id}) {
92-
$request->{attachment} = Bugzilla::Attachment->new({ id => $request->{attach_id}, cache => 1 });
93-
# check attachment visibility
94-
next if $request->{attachment}->isprivate && !$current_recipient->is_insider;
112+
}
113+
114+
# send emails
115+
116+
foreach my $recipient_id (sort keys %$requests) {
117+
my $recipient = Bugzilla::User->new({ id => $recipient_id, cache => 1 });
118+
my $has_key = $securemail && $recipient->public_key;
119+
my $has_private_bug = 0;
120+
121+
foreach my $target_login (keys %{ $requests->{$recipient_id} }) {
122+
my $rh = $requests->{$recipient_id}->{$target_login};
123+
$rh->{target} = Bugzilla::User->new({ name => $target_login, cache => 1 });
124+
foreach my $report (@reports) {
125+
foreach my $type (keys %{ $rh->{$report} }) {
126+
foreach my $request (@{ $rh->{$report}->{$type} }) {
127+
128+
_create_objects($request);
129+
130+
# we need to encrypt or censor emails which contain
131+
# non-public bugs
132+
if ($request->{bug}->is_private) {
133+
$has_private_bug = 1;
134+
$request->{bug}->{sanitise_bug} = !$securemail || !$has_key;
135+
}
136+
else {
137+
$request->{bug}->{sanitise_bug} = 0;
138+
}
139+
}
140+
}
141+
}
95142
}
96-
if (exists $request->{watcher_id}) {
97-
$request->{watcher} = Bugzilla::User->new({ id => $request->{watcher_id}, cache => 1 });
143+
my $encrypt = $securemail && $has_private_bug && $has_key;
144+
145+
# generate email
146+
my $template = Bugzilla->template_inner($recipient->setting('lang'));
147+
my $template_file = $args{template};
148+
my $vars = {
149+
recipient => $recipient,
150+
requests => $requests->{$recipient_id},
151+
date => $args{date},
152+
};
153+
154+
my ($header, $text);
155+
$template->process("email/request_nagging-$template_file-header.txt.tmpl", $vars, \$header)
156+
|| ThrowTemplateError($template->error());
157+
$header .= "\n";
158+
$template->process("email/request_nagging-$template_file.txt.tmpl", $vars, \$text)
159+
|| ThrowTemplateError($template->error());
160+
161+
my @parts = (
162+
Email::MIME->create(
163+
attributes => { content_type => "text/plain" },
164+
body => $text,
165+
)
166+
);
167+
if ($recipient->setting('email_format') eq 'html') {
168+
my $html;
169+
$template->process("email/request_nagging-$template_file.html.tmpl", $vars, \$html)
170+
|| ThrowTemplateError($template->error());
171+
push @parts, Email::MIME->create(
172+
attributes => { content_type => "text/html" },
173+
body => $html,
174+
);
98175
}
99176

100-
# add this request to the current user's list
101-
push(@$requests, $request);
102-
}
103-
send_email(%args, recipient => $current_recipient, requests => $requests);
104-
}
177+
my $email = Email::MIME->new($header);
178+
$email->header_set('X-Generated-By' => hostname());
179+
if (scalar(@parts) == 1) {
180+
$email->content_type_set($parts[0]->content_type);
181+
}
182+
else {
183+
$email->content_type_set('multipart/alternative');
184+
}
185+
$email->parts_set(\@parts);
186+
if ($encrypt) {
187+
$email->header_set('X-Bugzilla-Encrypt' => '1');
188+
}
105189

106-
sub send_email {
107-
my (%vars) = @_;
108-
my $vars = \%vars;
109-
return unless $vars->{recipient} && @{ $vars->{requests} };
110-
111-
my $request_list = delete $vars->{requests};
112-
113-
# if securemail is installed, we need to encrypt or censor emails which
114-
# contain non-public bugs
115-
my $default_user = Bugzilla::User->new();
116-
my $securemail = $vars->{recipient}->can('public_key');
117-
my $has_key = $securemail && $vars->{recipient}->public_key;
118-
# have to do this each time as objects are shared between requests
119-
my $has_private_bug = 0;
120-
foreach my $request (@{ $request_list }) {
121-
# rebless bug objects into our subclass
122-
bless($request->{bug}, 'Bugzilla::Extension::RequestNagger::Bug');
123-
# and tell that object to hide the summary if required
124-
if ($securemail && !$default_user->can_see_bug($request->{bug})) {
125-
$has_private_bug = 1;
126-
$request->{bug}->{secure_bug} = !$has_key;
190+
# send
191+
if ($DO_NOT_NAG) {
192+
# uncomment the following line to enable other extensions to
193+
# process this email, including encryption
194+
# Bugzilla::Hook::process('mailer_before_send', { email => $email });
195+
print $email->as_string, "\n";
127196
}
128197
else {
129-
$request->{bug}->{secure_bug} = 0;
198+
MessageToMTA($email);
130199
}
200+
201+
# nuke objects to avoid excessive memory usage
202+
$requests->{$recipient_id} = undef;
203+
Bugzilla->clear_request_cache();
131204
}
132-
my $encrypt = $securemail && $has_private_bug && $has_key;
133-
134-
# restructure the list to group by requestee then flag type
135-
my $requests = {};
136-
my %seen_types;
137-
foreach my $request (@{ $request_list }) {
138-
# by requestee
139-
my $requestee_login = $request->{requestee}->login;
140-
$requests->{$requestee_login} ||= {
141-
requestee => $request->{requestee},
142-
types => {},
143-
typelist => [],
144-
};
205+
}
145206

146-
# by flag type
147-
my $types = $requests->{$requestee_login}->{types};
148-
my $flag_type = $request->{flag_type};
149-
$types->{$flag_type} ||= [];
207+
sub _include_request {
208+
my ($request, $report) = @_;
150209

151-
push @{ $types->{$flag_type} }, $request;
152-
$seen_types{$requestee_login}{$flag_type} = 1;
153-
}
154-
foreach my $requestee_login (keys %seen_types) {
155-
my @flag_types;
156-
foreach my $flag_type (map { $_->{type} } FLAG_TYPES) {
157-
push @flag_types, $flag_type if $seen_types{$requestee_login}{$flag_type};
210+
my $recipient = Bugzilla::User->new({ id => $request->{recipient_id}, cache => 1 });
211+
212+
if ($report eq 'requestee') {
213+
# check recipient group membership
214+
my $group;
215+
foreach my $type (FLAG_TYPES) {
216+
next unless $type->{type} eq $request->{flag_type};
217+
$group = $type->{group};
218+
last;
158219
}
159-
$requests->{$requestee_login}->{typelist} = \@flag_types;
220+
return 0 unless $recipient->in_group($group);
160221
}
161-
$vars->{requests} = $requests;
162-
163-
# generate email
164-
my $template = Bugzilla->template_inner($vars->{recipient}->setting('lang'));
165-
my $template_file = $vars->{template};
166-
167-
my ($header, $text);
168-
$template->process("email/request_nagging-$template_file-header.txt.tmpl", $vars, \$header)
169-
|| ThrowTemplateError($template->error());
170-
$header .= "\n";
171-
$template->process("email/request_nagging-$template_file.txt.tmpl", $vars, \$text)
172-
|| ThrowTemplateError($template->error());
173-
174-
my @parts = (
175-
Email::MIME->create(
176-
attributes => { content_type => "text/plain" },
177-
body => $text,
178-
)
179-
);
180-
if ($vars->{recipient}->setting('email_format') eq 'html') {
181-
my $html;
182-
$template->process("email/request_nagging-$template_file.html.tmpl", $vars, \$html)
183-
|| ThrowTemplateError($template->error());
184-
push @parts, Email::MIME->create(
185-
attributes => { content_type => "text/html" },
186-
body => $html,
187-
);
222+
223+
# check bug visibility
224+
return 0 unless $recipient->can_see_bug($request->{bug_id});
225+
226+
# check attachment visibility
227+
if ($request->{attach_id}) {
228+
my $attachment = Bugzilla::Attachment->new({ id => $request->{attach_id}, cache => 1 });
229+
return 0 if $attachment->isprivate && !$recipient->is_insider;
188230
}
189231

190-
my $email = Email::MIME->new($header);
191-
$email->header_set('X-Generated-By' => hostname());
192-
if (scalar(@parts) == 1) {
193-
$email->content_type_set($parts[0]->content_type);
194-
} else {
195-
$email->content_type_set('multipart/alternative');
232+
return 1;
233+
}
234+
235+
sub _create_objects {
236+
my ($request) = @_;
237+
238+
$request->{recipient} = Bugzilla::User->new({ id => $request->{recipient_id}, cache => 1 });
239+
$request->{setter} = Bugzilla::User->new({ id => $request->{setter_id}, cache => 1 });
240+
241+
if (defined $request->{requestee_id}) {
242+
$request->{requestee} = Bugzilla::User->new({ id => $request->{requestee_id}, cache => 1 });
196243
}
197-
$email->parts_set(\@parts);
198-
if ($encrypt) {
199-
$email->header_set('X-Bugzilla-Encrypt' => '1');
244+
if (exists $request->{watcher_id}) {
245+
$request->{watcher} = Bugzilla::User->new({ id => $request->{watcher_id}, cache => 1 });
200246
}
201247

202-
# send
203-
if ($DO_NOT_NAG) {
204-
print $email->as_string, "\n";
205-
} else {
206-
MessageToMTA($email);
248+
$request->{bug} = Bugzilla::Extension::RequestNagger::Bug->new({ id => $request->{bug_id}, cache => 1 });
249+
$request->{flag} = Bugzilla::Flag->new({ id => $request->{flag_id}, cache => 1 });
250+
if (defined $request->{attach_id}) {
251+
$request->{attachment} = Bugzilla::Attachment->new({ id => $request->{attach_id}, cache => 1 });
207252
}
208253
}
209-

extensions/RequestNagger/lib/Bug.pm

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,22 @@ package Bugzilla::Extension::RequestNagger::Bug;
99

1010
use strict;
1111
use parent qw(Bugzilla::Bug);
12+
use feature 'state';
13+
14+
use Bugzilla::User;
1215

1316
sub short_desc {
1417
my ($self) = @_;
15-
return $self->{secure_bug} ? '(Secure bug)' : $self->SUPER::short_desc;
18+
return $self->{sanitise_bug} ? '(Secure bug)' : $self->SUPER::short_desc;
19+
}
20+
21+
sub is_private {
22+
my ($self) = @_;
23+
if (!exists $self->{is_private}) {
24+
state $default_user //= Bugzilla::User->new();
25+
$self->{is_private} = !$default_user->can_see_bug($self);
26+
}
27+
return $self->{is_private};
1628
}
1729

1830
sub tooltip {
@@ -21,7 +33,7 @@ sub tooltip {
2133
if ($self->bug_status eq 'RESOLVED') {
2234
$tooltip .= '/' . $self->resolution;
2335
}
24-
if (!$self->{secure_bug}) {
36+
if (!$self->{sanitise_bug}) {
2537
$tooltip .= ' ' . $self->product . ' :: ' . $self->component;
2638
}
2739
return $tooltip;

0 commit comments

Comments
 (0)