|
19 | 19 | use Bugzilla::Attachment; |
20 | 20 | use Bugzilla::Bug; |
21 | 21 | use Bugzilla::Constants; |
| 22 | +use Bugzilla::Hook; |
22 | 23 | use Bugzilla::Error; |
23 | 24 | use Bugzilla::Extension::RequestNagger::Constants; |
24 | 25 | use Bugzilla::Extension::RequestNagger::Bug; |
25 | 26 | use Bugzilla::Mailer; |
26 | 27 | use Bugzilla::User; |
27 | 28 | use Bugzilla::Util qw(format_time); |
28 | 29 | use Email::MIME; |
29 | | -use Sys::Hostname; |
| 30 | +use Sys::Hostname qw(hostname); |
30 | 31 |
|
31 | 32 | Bugzilla->usage_mode(USAGE_MODE_CMDLINE); |
32 | 33 |
|
|
42 | 43 |
|
43 | 44 | # send nags to requestees |
44 | 45 | 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, |
49 | 50 | ); |
50 | 51 |
|
51 | 52 | # send nags to watchers |
52 | 53 | 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, |
57 | 58 | ); |
58 | 59 |
|
59 | 60 | sub send_nags { |
60 | 61 | 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}; |
72 | 91 | } |
73 | 92 |
|
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 | + } |
81 | 111 | } |
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 | + } |
95 | 142 | } |
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 | + ); |
98 | 175 | } |
99 | 176 |
|
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 | + } |
105 | 189 |
|
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"; |
127 | 196 | } |
128 | 197 | else { |
129 | | - $request->{bug}->{secure_bug} = 0; |
| 198 | + MessageToMTA($email); |
130 | 199 | } |
| 200 | + |
| 201 | + # nuke objects to avoid excessive memory usage |
| 202 | + $requests->{$recipient_id} = undef; |
| 203 | + Bugzilla->clear_request_cache(); |
131 | 204 | } |
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 | +} |
145 | 206 |
|
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) = @_; |
150 | 209 |
|
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; |
158 | 219 | } |
159 | | - $requests->{$requestee_login}->{typelist} = \@flag_types; |
| 220 | + return 0 unless $recipient->in_group($group); |
160 | 221 | } |
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; |
188 | 230 | } |
189 | 231 |
|
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 }); |
196 | 243 | } |
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 }); |
200 | 246 | } |
201 | 247 |
|
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 }); |
207 | 252 | } |
208 | 253 | } |
209 | | - |
|
0 commit comments