Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
1.25% |
2 / 160 |
|
4.76% |
1 / 21 |
CRAP | |
0.00% |
0 / 1 |
|
1.25% |
2 / 160 |
|
4.76% |
1 / 21 |
3075.86 | |
0.00% |
0 / 1 |
|
get_id | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
get_queue_object_name | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
is_enabled | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
init | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
set_use_queue | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
set_addresses | |
50.00% |
1 / 2 |
|
0.00% |
0 / 1 |
4.12 | |||
to | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
30 | |||
cc | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
bcc | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
reply_to | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
from | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
subject | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
anti_abuse_headers | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
set_mail_priority | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
build_headers | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
20 | |||
set_dsn | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
20 | |||
get_dsn | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
set_transport | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 | |||
process_queue | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
90 | |||
get_transport | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
send | |
0.00% |
0 / 52 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | /** |
3 | * |
4 | * This file is part of the phpBB Forum Software package. |
5 | * |
6 | * @copyright (c) phpBB Limited <https://www.phpbb.com> |
7 | * @license GNU General Public License, version 2 (GPL-2.0) |
8 | * |
9 | * For full copyright and license information, please see |
10 | * the docs/CREDITS.txt file. |
11 | * |
12 | */ |
13 | |
14 | namespace phpbb\messenger\method; |
15 | |
16 | use Symfony\Component\Mailer\Transport; |
17 | use Symfony\Component\Mailer\Mailer; |
18 | use Symfony\Component\Mime\Address; |
19 | use Symfony\Component\Mime\Email as symfony_email; |
20 | use Symfony\Component\Mime\Header\Headers; |
21 | |
22 | /** |
23 | * Messenger class |
24 | */ |
25 | class email extends base |
26 | { |
27 | /** @var array */ |
28 | private const PRIORITY_MAP = [ |
29 | symfony_email::PRIORITY_HIGHEST => 'Highest', |
30 | symfony_email::PRIORITY_HIGH => 'High', |
31 | symfony_email::PRIORITY_NORMAL => 'Normal', |
32 | symfony_email::PRIORITY_LOW => 'Low', |
33 | symfony_email::PRIORITY_LOWEST => 'Lowest', |
34 | ]; |
35 | |
36 | /** |
37 | * @var string |
38 | * |
39 | * Symfony Mailer transport DSN |
40 | */ |
41 | protected $dsn = ''; |
42 | |
43 | /** @var symfony_email */ |
44 | protected $email; |
45 | |
46 | /** @var Address */ |
47 | protected $from; |
48 | |
49 | /** @var Headers */ |
50 | protected $headers; |
51 | |
52 | /** |
53 | * @var int |
54 | * |
55 | * Possible values are: |
56 | * symfony_email::PRIORITY_HIGHEST |
57 | * symfony_email::PRIORITY_HIGH |
58 | * symfony_email::PRIORITY_NORMAL |
59 | * symfony_email::PRIORITY_LOW |
60 | * symfony_email::PRIORITY_LOWEST |
61 | */ |
62 | protected $mail_priority = symfony_email::PRIORITY_NORMAL; |
63 | |
64 | /** @var \phpbb\messenger\queue */ |
65 | protected $queue; |
66 | |
67 | /** @var Address */ |
68 | protected $reply_to; |
69 | |
70 | /** @var \Symfony\Component\Mailer\Transport\AbstractTransport */ |
71 | protected $transport; |
72 | |
73 | /** |
74 | * {@inheritDoc} |
75 | */ |
76 | public function get_id(): int |
77 | { |
78 | return self::NOTIFY_EMAIL; |
79 | } |
80 | |
81 | /** |
82 | * {@inheritDoc} |
83 | */ |
84 | public function get_queue_object_name(): string |
85 | { |
86 | return 'email'; |
87 | } |
88 | |
89 | /** |
90 | * {@inheritDoc} |
91 | */ |
92 | public function is_enabled(): bool |
93 | { |
94 | return (bool) $this->config['email_enable']; |
95 | } |
96 | |
97 | /** |
98 | * {@inheritDoc} |
99 | */ |
100 | public function init(): void |
101 | { |
102 | $this->email = new symfony_email(); |
103 | $this->headers = $this->email->getHeaders(); |
104 | $this->subject = $this->msg = ''; |
105 | $this->mail_priority = symfony_email::PRIORITY_NORMAL; |
106 | |
107 | $this->additional_headers = []; |
108 | $this->use_queue = true; |
109 | unset($this->template, $this->reply_to, $this->from); |
110 | } |
111 | |
112 | /** |
113 | * {@inheritdoc} |
114 | */ |
115 | public function set_use_queue(bool $use_queue = true): void |
116 | { |
117 | $this->use_queue = !$this->config['email_package_size'] ? false : $use_queue; |
118 | } |
119 | |
120 | /** |
121 | * {@inheritDoc} |
122 | */ |
123 | public function set_addresses(array $user_row): void |
124 | { |
125 | if (!empty($user_row['user_email'])) |
126 | { |
127 | $this->to($user_row['user_email'], $user_row['username'] ?: ''); |
128 | } |
129 | } |
130 | |
131 | /** |
132 | * Sets email address to send to |
133 | * |
134 | * @param string $address Email "To" recipient address |
135 | * @param string $realname Email "To" recipient name |
136 | * @return void |
137 | */ |
138 | public function to(string $address, string $realname = ''): void |
139 | { |
140 | if (!$address = trim($address)) |
141 | { |
142 | return; |
143 | } |
144 | |
145 | // If empty sendmail_path on windows, PHP changes the to line |
146 | $windows_empty_sendmail_path = !$this->config['smtp_delivery'] && DIRECTORY_SEPARATOR == '\\'; |
147 | |
148 | $to = new Address($address, $windows_empty_sendmail_path ? '' : trim($realname)); |
149 | $this->email->getTo() ? $this->email->addTo($to) : $this->email->to($to); |
150 | } |
151 | |
152 | /** |
153 | * Sets cc address to send to |
154 | * |
155 | * @param string $address Email carbon copy recipient address |
156 | * @param string $realname Email carbon copy recipient name |
157 | * @return void |
158 | */ |
159 | public function cc(string $address, string $realname = ''): void |
160 | { |
161 | if (!$address = trim($address)) |
162 | { |
163 | return; |
164 | } |
165 | |
166 | $cc = new Address($address, trim($realname)); |
167 | $this->email->getCc() ? $this->email->addCc($cc) : $this->email->cc($cc); |
168 | } |
169 | |
170 | /** |
171 | * Sets bcc address to send to |
172 | * |
173 | * @param string $address Email black carbon copy recipient address |
174 | * @param string $realname Email black carbon copy recipient name |
175 | * @return void |
176 | */ |
177 | public function bcc(string $address, string $realname = ''): void |
178 | { |
179 | if (!$address = trim($address)) |
180 | { |
181 | return; |
182 | } |
183 | |
184 | $bcc = new Address($address, trim($realname)); |
185 | $this->email->getBcc() ? $this->email->addBcc($bcc) : $this->email->bcc($bcc); |
186 | } |
187 | |
188 | /** |
189 | * Set the reply to address |
190 | * |
191 | * @param string $address Email "Reply to" address |
192 | * @param string $realname Email "Reply to" recipient name |
193 | * @return void |
194 | */ |
195 | public function reply_to(string $address, string $realname = ''): void |
196 | { |
197 | if (!$address = trim($address)) |
198 | { |
199 | return; |
200 | } |
201 | |
202 | $this->reply_to = new Address($address, trim($realname)); |
203 | $this->email->getReplyTo() ? $this->email->addReplyTo($this->reply_to) : $this->email->replyTo($this->reply_to); |
204 | } |
205 | |
206 | /** |
207 | * Set the from address |
208 | * |
209 | * @param string $address Email "from" address |
210 | * @param string $realname Email "from" recipient name |
211 | * @return void |
212 | */ |
213 | public function from(string $address, string $realname = ''): void |
214 | { |
215 | if (!$address = trim($address)) |
216 | { |
217 | return; |
218 | } |
219 | |
220 | $this->from = new Address($address, trim($realname)); |
221 | $this->email->getFrom() ? $this->email->addFrom($this->from) : $this->email->from($this->from); |
222 | } |
223 | |
224 | /** |
225 | * Set up subject for mail |
226 | * |
227 | * @param string $subject Email subject |
228 | * @return void |
229 | */ |
230 | public function subject(string $subject = ''): void |
231 | { |
232 | parent::subject(trim($subject)); |
233 | $this->email->subject($this->subject); |
234 | } |
235 | |
236 | /** |
237 | * Adds X-AntiAbuse headers |
238 | * |
239 | * @param \phpbb\config\config $config Config object |
240 | * @param \phpbb\user $user User object |
241 | * @return void |
242 | */ |
243 | public function anti_abuse_headers(\phpbb\config\config $config, \phpbb\user $user): void |
244 | { |
245 | $this->headers->addHeader('X-AntiAbuse', 'Board servername - ' . $config['server_name']); |
246 | $this->headers->addHeader('X-AntiAbuse', 'User_id - ' . $user->data['user_id']); |
247 | $this->headers->addHeader('X-AntiAbuse', 'Username - ' . $user->data['username']); |
248 | $this->headers->addHeader('X-AntiAbuse', 'User IP - ' . $user->ip); |
249 | } |
250 | |
251 | /** |
252 | * Set the email priority |
253 | * |
254 | * Possible values are: |
255 | * symfony_email::PRIORITY_HIGHEST = 1 |
256 | * symfony_email::PRIORITY_HIGH = 2 |
257 | * symfony_email::PRIORITY_NORMAL = 3 |
258 | * symfony_email::PRIORITY_LOW = 4 |
259 | * symfony_email::PRIORITY_LOWEST = 5 |
260 | * |
261 | * @param int $priority Email priority level |
262 | * @return void |
263 | */ |
264 | public function set_mail_priority(int $priority = symfony_email::PRIORITY_NORMAL): void |
265 | { |
266 | $this->email->priority($priority); |
267 | } |
268 | |
269 | /** |
270 | * Set email headers |
271 | * |
272 | * @return void |
273 | */ |
274 | protected function build_headers(): void |
275 | { |
276 | |
277 | $board_contact = trim($this->config['board_contact']); |
278 | $contact_name = html_entity_decode($this->config['board_contact_name'], ENT_COMPAT); |
279 | |
280 | if (empty($this->email->getReplyTo())) |
281 | { |
282 | $this->reply_to($board_contact, $contact_name); |
283 | } |
284 | |
285 | if (empty($this->email->getFrom())) |
286 | { |
287 | $this->from($board_contact, $contact_name); |
288 | } |
289 | |
290 | $this->email->priority($this->mail_priority); |
291 | |
292 | $headers = [ |
293 | 'Return-Path' => new Address($this->config['board_email']), |
294 | 'Sender' => new Address($this->config['board_email']), |
295 | 'X-MSMail-Priority' => self::PRIORITY_MAP[$this->mail_priority], |
296 | 'X-Mailer' => 'phpBB', |
297 | 'X-MimeOLE' => 'phpBB', |
298 | 'X-phpBB-Origin' => 'phpbb://' . str_replace(['http://', 'https://'], ['', ''], generate_board_url()), |
299 | ]; |
300 | |
301 | // Add additional headers |
302 | $headers = array_merge($headers, $this->additional_headers); |
303 | |
304 | /** |
305 | * Event to modify email header entries |
306 | * |
307 | * @event core.modify_email_headers |
308 | * @var array headers Array containing email header entries |
309 | * @since 3.1.11-RC1 |
310 | */ |
311 | $vars = ['headers']; |
312 | extract($this->dispatcher->trigger_event('core.modify_email_headers', compact($vars))); |
313 | |
314 | foreach ($headers as $header => $value) |
315 | { |
316 | $this->headers->addHeader($header, $value); |
317 | } |
318 | |
319 | } |
320 | |
321 | /** |
322 | * Generates valid DSN for Symfony Mailer transport |
323 | * |
324 | * @param string $dsn Symfony Mailer transport DSN |
325 | * @return void |
326 | */ |
327 | public function set_dsn(string $dsn = ''): void |
328 | { |
329 | if (!empty($dsn)) |
330 | { |
331 | $this->dsn = $dsn; |
332 | } |
333 | else if ($this->config['smtp_delivery']) |
334 | { |
335 | if (empty($this->config['smtp_host'])) |
336 | { |
337 | $this->dsn = 'null://null'; |
338 | } |
339 | else |
340 | { |
341 | $user = urlencode($this->config['smtp_username']); |
342 | $password = urlencode($this->config['smtp_password']); |
343 | $smtp_host = urlencode($this->config['smtp_host']); |
344 | $smtp_port = $this->config['smtp_port']; |
345 | |
346 | $this->dsn = "smtp://$user:$password@$smtp_host:$smtp_port"; |
347 | } |
348 | } |
349 | else |
350 | { |
351 | $this->dsn = 'sendmail://default'; |
352 | } |
353 | } |
354 | |
355 | /** |
356 | * Get Symfony Mailer transport DSN |
357 | * |
358 | * @return string |
359 | */ |
360 | public function get_dsn(): string |
361 | { |
362 | return $this->dsn; |
363 | } |
364 | |
365 | /** |
366 | * Generates a valid transport to send email |
367 | * |
368 | * @return void |
369 | */ |
370 | public function set_transport(): void |
371 | { |
372 | if (empty($this->dsn)) |
373 | { |
374 | $this->set_dsn(); |
375 | } |
376 | |
377 | $this->transport = Transport::fromDsn($this->dsn); |
378 | |
379 | if ($this->config['smtp_delivery'] && method_exists($this->transport, 'getStream')) |
380 | { |
381 | // Set ssl context options, see http://php.net/manual/en/context.ssl.php |
382 | $options['ssl'] = [ |
383 | 'verify_peer' => (bool) $this->config['smtp_verify_peer'], |
384 | 'verify_peer_name' => (bool) $this->config['smtp_verify_peer_name'], |
385 | 'allow_self_signed' => (bool) $this->config['smtp_allow_self_signed'], |
386 | ]; |
387 | $this->transport->getStream()->setStreamOptions($options); |
388 | } |
389 | } |
390 | |
391 | /** |
392 | * {@inheritDoc} |
393 | */ |
394 | public function process_queue(array &$queue_data): void |
395 | { |
396 | $queue_object_name = $this->get_queue_object_name(); |
397 | $messages_count = count($queue_data[$queue_object_name]['data']); |
398 | |
399 | if (!$this->is_enabled() || !$messages_count) |
400 | { |
401 | unset($queue_data[$queue_object_name]); |
402 | return; |
403 | } |
404 | |
405 | @set_time_limit(0); |
406 | |
407 | $package_size = $queue_data[$queue_object_name]['package_size'] ?? 0; |
408 | $num_items = (!$package_size || $messages_count < $package_size) ? $messages_count : $package_size; |
409 | $mailer = new Mailer($this->transport); |
410 | |
411 | for ($i = 0; $i < $num_items; $i++) |
412 | { |
413 | // Make variables available... |
414 | extract(array_shift($queue_data[$queue_object_name]['data'])); |
415 | |
416 | $break = false; |
417 | /** |
418 | * Event to send message via external transport |
419 | * |
420 | * @event core.notification_message_process |
421 | * @var string break Flag indicating if the function return after hook |
422 | * @var string email The Symfony Email object |
423 | * @since 3.2.4-RC1 |
424 | * @changed 4.0.0-a1 Added vars: email. Removed vars: addresses, subject, msg. |
425 | */ |
426 | $vars = [ |
427 | 'break', |
428 | 'email', |
429 | ]; |
430 | extract($this->dispatcher->trigger_event('core.notification_message_process', compact($vars))); |
431 | |
432 | if (!$break) |
433 | { |
434 | try |
435 | { |
436 | $mailer->send($email); |
437 | } |
438 | catch (\Symfony\Component\Mailer\Exception\TransportExceptionInterface $e) |
439 | { |
440 | $this->error($e->getDebug()); |
441 | continue; |
442 | } |
443 | } |
444 | } |
445 | |
446 | // No more data for this object? Unset it |
447 | if (!count($queue_data[$queue_object_name]['data'])) |
448 | { |
449 | unset($queue_data[$queue_object_name]); |
450 | } |
451 | } |
452 | |
453 | /** |
454 | * Get mailer transport object |
455 | * |
456 | * @return \Symfony\Component\Mailer\Transport\TransportInterface Symfony Mailer transport object |
457 | */ |
458 | public function get_transport(): \Symfony\Component\Mailer\Transport\TransportInterface |
459 | { |
460 | return $this->transport; |
461 | } |
462 | |
463 | /** |
464 | * {@inheritDoc} |
465 | */ |
466 | public function send(): bool |
467 | { |
468 | $this->prepare_message(); |
469 | |
470 | $this->email->subject($this->subject); |
471 | $this->email->text($this->msg); |
472 | |
473 | $break = false; |
474 | $subject = $this->subject; |
475 | $msg = $this->msg; |
476 | $email = $this->email; |
477 | /** |
478 | * Event to send message via external transport |
479 | * |
480 | * @event core.notification_message_email |
481 | * @var bool break Flag indicating if the function return after hook |
482 | * @var string subject The message subject |
483 | * @var string msg The message text |
484 | * @var string email The Symfony Email object |
485 | * @since 3.2.4-RC1 |
486 | * @changed 4.0.0-a1 Added vars: email. Removed vars: addresses |
487 | */ |
488 | $vars = [ |
489 | 'break', |
490 | 'subject', |
491 | 'msg', |
492 | 'email', |
493 | ]; |
494 | extract($this->dispatcher->trigger_event('core.notification_message_email', compact($vars))); |
495 | $this->email = $email; |
496 | |
497 | $this->build_headers(); |
498 | |
499 | if ($break) |
500 | { |
501 | return true; |
502 | } |
503 | |
504 | // Send message ... |
505 | if (!$this->use_queue) |
506 | { |
507 | $mailer = new Mailer($this->transport); |
508 | |
509 | $subject = $this->subject; |
510 | $msg = $this->msg; |
511 | $headers = $this->headers; |
512 | $email = $this->email; |
513 | /** |
514 | * Modify data before sending out emails with PHP's mail function |
515 | * |
516 | * @event core.phpbb_mail_before |
517 | * @var string email The Symfony Email object |
518 | * @var string subject The message subject |
519 | * @var string msg The message text |
520 | * @var string headers The email headers |
521 | * @since 3.3.6-RC1 |
522 | * @changed 4.0.0-a1 Added vars: email. Removed vars: to, eol, additional_parameters. |
523 | */ |
524 | $vars = [ |
525 | 'email', |
526 | 'subject', |
527 | 'msg', |
528 | 'headers', |
529 | ]; |
530 | extract($this->dispatcher->trigger_event('core.phpbb_mail_before', compact($vars))); |
531 | |
532 | $this->subject = $subject; |
533 | $this->msg = $msg; |
534 | $this->headers = $headers; |
535 | $this->email = $email; |
536 | |
537 | try |
538 | { |
539 | $mailer->send($this->email); |
540 | } |
541 | catch (\Symfony\Component\Mailer\Exception\TransportExceptionInterface $e) |
542 | { |
543 | $this->error($e->getDebug()); |
544 | return false; |
545 | } |
546 | |
547 | /** |
548 | * Execute code after sending out emails with PHP's mail function |
549 | * |
550 | * @event core.phpbb_mail_after |
551 | * @var string email The Symfony Email object |
552 | * @var string subject The message subject |
553 | * @var string msg The message text |
554 | * @var string headers The email headers |
555 | * @since 3.3.6-RC1 |
556 | * @changed 4.0.0-a1 Added vars: email. Removed vars: to, eol, additional_parameters, $result. |
557 | */ |
558 | $vars = [ |
559 | 'email', |
560 | 'subject', |
561 | 'msg', |
562 | 'headers', |
563 | ]; |
564 | extract($this->dispatcher->trigger_event('core.phpbb_mail_after', compact($vars))); |
565 | } |
566 | else |
567 | { |
568 | $this->queue->init('email', $this->config['email_package_size']); |
569 | $this->queue->put('email', [ |
570 | 'email' => $this->email, |
571 | ]); |
572 | } |
573 | |
574 | // Reset the object |
575 | $this->init(); |
576 | |
577 | return true; |
578 | } |
579 | } |