Mail::sendMail()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 5
nc 3
nop 0
dl 0
loc 8
ccs 0
cts 5
cp 0
crap 12
rs 10
c 1
b 0
f 0
1
<?php
2
namespace phpbu\App\Log;
3
4
use phpbu\App\Cli\Statistics;
5
use phpbu\App\Exception;
6
use phpbu\App\Event;
7
use phpbu\App\Listener;
8
use phpbu\App\Result;
9
use phpbu\App\Log\MailTemplate as TPL;
10
use phpbu\App\Util\Arr;
11
use phpbu\App\Util\Str;
12
use PHPMailer\PHPMailer\PHPMailer;
13
14
/**
15
 * Mail Logger
16
 *
17
 * @package    phpbu
18
 * @subpackage Log
19
 * @author     Sebastian Feldmann <[email protected]>
20
 * @copyright  Sebastian Feldmann <[email protected]>
21
 * @license    https://opensource.org/licenses/MIT The MIT License (MIT)
22
 * @link       https://phpbu.de/
23
 * @since      Class available since Release 1.0.0
24
 */
25
class Mail implements Listener, Logger
26
{
27
    /**
28
     * Mailer instance
29
     *
30
     * @var \PHPMailer\PHPMailer\PHPMailer;
31
     */
32
    protected $mailer;
33
34
    /**
35
     * Mail subject
36
     *
37
     * @var string
38
     */
39
    protected $subject;
40
41
    /**
42
     * From email address
43
     *
44
     * @var string
45
     */
46
    protected $senderMail;
47
48
    /**
49
     * From name
50
     *
51
     * @var string
52
     */
53
    protected $senderName;
54
55
    /**
56
     * Transport type [mail|smtp|null]
57
     *
58
     * @var string
59
     */
60
    protected $transportType;
61
62
    /**
63
     * List of mail recipients
64
     *
65
     * @var array<string>
66
     */
67
    protected $recipients = [];
68
69
    /**
70
     * Amount of executed backups
71
     *
72
     * @var integer
73
     */
74
    private $numBackups = 0;
75
76
    /**
77
     * Amount of executed checks
78
     *
79
     * @var integer
80
     */
81
    private $numChecks = 0;
82
83
    /**
84
     * Amount of executed Syncs
85
     *
86
     * @var integer
87
     */
88
    private $numSyncs = 0;
89
90
    /**
91
     * Amount of executed Crypts
92
     *
93
     * @var integer
94
     */
95
    private $numCrypts = 0;
96
97
    /**
98
     * Amount of executed Cleanups
99
     *
100
     * @var integer
101
     */
102
    private $numCleanups = 0;
103
104
    /**
105
     * Send mail only if there was an error
106
     *
107
     * @var bool
108
     */
109
    private $sendOnlyOnError = false;
110
111
    /**
112
     * Send mails on simulation runs
113
     *
114
     * @var bool
115
     */
116
    private $sendSimulating = true;
117
118
    /**
119
     * Is current execution a simulation
120
     *
121
     * @var bool
122
     */
123
    private $isSimulation = false;
124
125
    /**
126
     * Returns an array of event names this subscriber wants to listen to
127
     *
128
     * The array keys are event names and the value can be:
129
     *
130
     *  * The method name to call (priority defaults to 0)
131
     *  * An array composed of the method name to call and the priority
132
     *  * An array of arrays composed of the method names to call and respective
133
     *    priorities, or 0 if unset
134
     *
135
     * @return array The event names to listen to
136
     */
137 1
    public static function getSubscribedEvents(): array
138
    {
139
        return [
140 1
            'phpbu.backup_start'  => 'onBackupStart',
141
            'phpbu.check_start'   => 'onCheckStart',
142
            'phpbu.crypt_start'   => 'onCryptStart',
143
            'phpbu.sync_start'    => 'onSyncStart',
144
            'phpbu.cleanup_start' => 'onCleanupStart',
145
            'phpbu.app_end'       => 'onPhpbuEnd',
146
        ];
147
    }
148
149
    /**
150
     * Setup the Logger
151
     *
152
     * @see    \phpbu\App\Log\Logger::setup
153
     * @param  array $options
154
     * @throws \phpbu\App\Exception
155
     */
156 12
    public function setup(array $options)
157
    {
158 12
        if (empty($options['recipients'])) {
159 1
            throw new Exception('no recipients given');
160
        }
161 11
        $mails                 = $options['recipients'];
162 11
        $server                = gethostname();
163 11
        $this->sendOnlyOnError = Str::toBoolean(Arr::getValue($options, 'sendOnlyOnError'), false);
164 11
        $this->sendSimulating  = Str::toBoolean(Arr::getValue($options, 'sendOnSimulation'), true);
165 11
        $this->subject         = Arr::getValue($options, 'subject', 'PHPBU backup report from ' . $server);
166 11
        $this->senderMail      = Arr::getValue($options, 'sender.mail', 'phpbu@' . $server);
167 11
        $this->senderName      = Arr::getValue($options, 'sender.name');
168 11
        $this->transportType   = Arr::getValue($options, 'transport', 'mail');
169 11
        $this->recipients      = array_map('trim', explode(';', $mails));
170 11
        $this->isSimulation    = Arr::getValue($options, '__simulate__', false);
171
172
        // create transport an mailer
173 11
        $this->mailer = new PHPMailer();
174 11
        $this->setupMailer($this->transportType, $options);
175 9
    }
176
177
    /**
178
     * Handle the phpbu end event
179
     *
180
     * @param  \phpbu\App\Event\App\End $event
181
     * @throws \phpbu\App\Exception
182
     * @throws \PHPMailer\PHPMailer\Exception
183
     */
184 4
    public function onPhpbuEnd(Event\App\End $event)
185
    {
186 4
        $result = $event->getResult();
187
188 4
        if ($this->shouldMailBeSend($result) === false) {
189 4
            return;
190 4
        }
191 4
192 4
        $header  = $this->getHeaderHtml();
193 4
        $status  = $this->getStatusHtml($result);
194 4
        $errors  = $this->getErrorHtml($result);
195 4
        $info    = $this->getInfoHtml($result);
196 4
        $footer  = $this->getFooterHtml();
197 4
        $body    = '<html><body ' . TPL::getSnippet('sBody') . '>'
198 4
                 . $header
199 4
                 . $status
200 4
                 . $errors
201 4
                 . $info
202
                 . $footer
203 4
                 . '</body></html>';
204 4
        $state   = $result->allOk() ? 'OK' : ($result->backupOkButSkipsOrFails() ? 'WARNING' : 'ERROR');
205 4
206
        $this->mailer->Subject = $this->subject . ' [' . ($this->isSimulation ? 'SIMULATION' : $state) . ']';
207 4
        $this->mailer->setFrom($this->senderMail, $this->senderName);
208 4
        $this->mailer->msgHTML($body);
209
210
        foreach ($this->recipients as $recipient) {
211 4
            $this->mailer->addAddress($recipient);
212
        }
213
214
        if ($this->transportType !== 'null') {
215 4
            $this->sendMail();
216
        }
217
    }
218
219
    /**
220
     * Backup start event.
221
     *
222 4
     * @param \phpbu\App\Event\Backup\Start $event
223
     */
224 4
    public function onBackupStart(Event\Backup\Start $event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

224
    public function onBackupStart(/** @scrutinizer ignore-unused */ Event\Backup\Start $event)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
225 4
    {
226
        $this->numBackups++;
227
    }
228
229
    /**
230
     * Check start event.
231
     *
232 1
     * @param \phpbu\App\Event\Check\Start $event
233
     */
234 1
    public function onCheckStart(Event\Check\Start $event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

234
    public function onCheckStart(/** @scrutinizer ignore-unused */ Event\Check\Start $event)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
235 1
    {
236
        $this->numChecks++;
237
    }
238
239
    /**
240
     * Crypt start event.
241
     *
242 1
     * @param \phpbu\App\Event\Crypt\Start $event
243
     */
244 1
    public function onCryptStart(Event\Crypt\Start $event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

244
    public function onCryptStart(/** @scrutinizer ignore-unused */ Event\Crypt\Start $event)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
245 1
    {
246
        $this->numCrypts++;
247
    }
248
249
    /**
250
     * Sync start event.
251
     *
252 1
     * @param \phpbu\App\Event\Sync\Start $event
253
     */
254 1
    public function onSyncStart(Event\Sync\Start $event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

254
    public function onSyncStart(/** @scrutinizer ignore-unused */ Event\Sync\Start $event)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
255 1
    {
256
        $this->numSyncs++;
257
    }
258
259
    /**
260
     * Cleanup start event.
261
     *
262 1
     * @param \phpbu\App\Event\Cleanup\Start $event
263
     */
264 1
    public function onCleanupStart(Event\Cleanup\Start $event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

264
    public function onCleanupStart(/** @scrutinizer ignore-unused */ Event\Cleanup\Start $event)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
265 1
    {
266
        $this->numCleanups++;
267
    }
268
269
    /**
270
     * Configure PHPMailer
271
     *
272
     * @param  string                         $type
273
     * @param  array                          $options
274 11
     * @throws \phpbu\App\Exception
275
     */
276 11
    protected function setupMailer($type, array $options)
277 11
    {
278 4
        switch ($type) {
279 4
            case 'null':
280
                $this->isSimulation = true;
281 7
                break;
282 2
283 1
            case 'smtp':
284
                $this->setupSmtpMailer($options);
285 5
                break;
286 3
287 4
            case 'mail':
288 4
            case 'sendmail':
289
                $this->setupSendmailMailer($options);
290
                break;
291
292 1
            // UPS! no transport given
293
            default:
294 9
                throw new Exception(sprintf('mail transport not supported: \'%s\'', $type));
295
        }
296
    }
297
298
    /**
299
     * Should a mail be send
300
     *
301
     * @param  \phpbu\App\Result $result
302 4
     * @return bool
303
     */
304
    protected function shouldMailBeSend(Result $result) : bool
305
    {
306
        // send mails if
307
        // there is an error or send error only is inactive
308 4
        // and
309
        // simulation settings do not prevent sending
310
        return (!$this->sendOnlyOnError || !$result->allOk()) && ($this->sendSimulating || !$this->isSimulation);
311
    }
312
313
    /**
314
     * Setup smtp mailing
315
     *
316
     * @param  array $options
317
     * @return void
318 2
     * @throws \phpbu\App\Exception
319
     */
320 2
    protected function setupSmtpMailer(array $options)
321 1
    {
322
        if (!isset($options['smtp.host'])) {
323 1
            throw new Exception('option \'smtp.host\' ist missing');
324 1
        }
325 1
        $host       = $options['smtp.host'];
326 1
        $port       = Arr::getValue($options, 'smtp.port', 25);
327 1
        $username   = Arr::getValue($options, 'smtp.username');
328
        $password   = Arr::getValue($options, 'smtp.password');
329 1
        $encryption = Arr::getValue($options, 'smtp.encryption');
330 1
331 1
        $this->mailer->isSMTP();
332
        $this->mailer->Host     = $host;
333 1
        $this->mailer->Port     = $port;
334 1
335 1
        if ($username && $password) {
336 1
            $this->mailer->SMTPAuth = true;
337
            $this->mailer->Username = $username;
338 1
            $this->mailer->Password = $password;
339 1
        }
340
        if ($encryption) {
341 1
            $this->mailer->SMTPSecure = $encryption;
342
        }
343
    }
344
345
    /**
346
     * Setup the php mail transport
347
     *
348
     * @param  array $options
349 4
     * @return void
350
     */
351
    protected function setupSendmailMailer(array $options)
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

351
    protected function setupSendmailMailer(/** @scrutinizer ignore-unused */ array $options)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
352 4
    {
353
        // nothing to do here
354
    }
355
356
    /**
357
     * Send the email
358
     *
359
     * @throws \phpbu\App\Exception
360
     */
361
    protected function sendMail()
362
    {
363
        try {
364
            if (!$this->mailer->send()) {
365
                throw new Exception($this->mailer->ErrorInfo);
366
            }
367
        } catch (\Exception $e) {
368
            throw new Exception($e->getMessage());
369
        }
370
    }
371
372
    /**
373
     * Return mail header html
374
     *
375
     * @return string
376 4
     * @throws \phpbu\App\Exception
377
     */
378 4
    protected function getHeaderHtml()
379 4
    {
380
        return '<table ' . TPL::getSnippet('sTableContent') . '><tr><td ' . TPL::getSnippet('sTableContentCol') . '>' .
381
               '<table ' . TPL::getSnippet('sTableHeader') . '><tr><td>PHPBU - backup report</td></tr></table>';
382
    }
383
384
    /**
385
     * Return mail status html
386
     *
387
     * @param  \phpbu\App\Result $result
388
     * @return string
389 4
     * @throws \phpbu\App\Exception
390
     */
391 4
    protected function getStatusHtml(Result $result)
392 1
    {
393 1
        if (count($result->getBackups()) === 0) {
394 3
            $color  = TPL::getSnippet('cStatusWARN');
395 1
            $status = 'WARNING';
396 1
        } elseif ($result->allOk()) {
397 2
            $color  = TPL::getSnippet('cStatusOK');
398 1
            $status = 'OK';
399 1
        } elseif ($result->backupOkButSkipsOrFails()) {
400
            $color  = TPL::getSnippet('cStatusWARN');
401 1
            $status = 'WARNING';
402 1
        } else {
403
            $color  = TPL::getSnippet('cStatusFAIL');
404 4
            $status = 'FAILURE';
405 4
        }
406 4
        $info = sprintf(
407 4
            '(%d %s, %d %s, %d %s, %d %s, %d %s)',
408 4
            count($result->getBackups()),
409 4
            Str::appendPluralS('backup', count($result->getBackups())),
410 4
            $this->numChecks,
411 4
            Str::appendPluralS('check', $this->numChecks),
412 4
            $this->numCrypts,
413 4
            Str::appendPluralS('crypt', $this->numCrypts),
414 4
            $this->numSyncs,
415 4
            Str::appendPluralS('sync', $this->numSyncs),
416
            $this->numCleanups,
417 4
            Str::appendPluralS('cleanup', $this->numCleanups)
418 4
        );
419 4
        $html = '<table ' . sprintf(TPL::getSnippet('sTableStatus'), $color) . '>' .
420 4
                 '<tr><td>' .
421 4
                  '<span ' . TPL::getSnippet('sTableStatusText') . '>' . date('Y-m-d H:i') . '</span>' .
422 4
                  '<h1 ' . TPL::getSnippet('sTableStatusHead') . '>' . $status . '</h1>' .
423 4
                  '<span ' . TPL::getSnippet('sTableStatusText') . '>' . $info . '</span>' .
424
                 '</td></tr>' .
425 4
                '</table>';
426
427
        return $html;
428
    }
429
430
    /**
431
     * Get error information
432
     *
433
     * @param  \phpbu\App\Result $result
434
     * @return string
435 4
     * @throws \phpbu\App\Exception
436
     */
437 4
    protected function getErrorHtml(Result $result)
438 4
    {
439 4
        $errors = $result->getErrors();
440 1
441
        if (count($errors) === 0) {
442 1
            return '';
443 1
        }
444 1
445 1
        $html  = '';
446 1
        $html .= '<table ' . TPL::getSnippet('sTableError') . '>';
447 1
        /* @var $e Exception */
448 1
        foreach ($errors as $e) {
449 1
            $html .= '<tr><td ' . TPL::getSnippet('sTableErrorCol') . '>' .
450
                sprintf(
451 1
                    "Exception '%s' with message '%s' in %s:%d",
452
                    get_class($e),
453 1
                    $e->getMessage(),
454
                    $e->getFile(),
455 4
                    $e->getLine()
456
                ) .
457
                '</td></tr>';
458
        }
459
        $html .= '</table>';
460
461
        return $html;
462
    }
463
464
    /**
465 4
     * Return backup html information
466
     *
467 4
     * @param  \phpbu\App\Result $result
468 4
     * @return string
469 4
     * @throws \phpbu\App\Exception
470 3
     */
471
    protected function getInfoHtml(Result $result)
472 3
    {
473 3
        $backups = $result->getBackups();
474 1
475 1
        if (count($backups) === 0) {
476 2
            return '';
477 1
        }
478 1
479
        $html  = '';
480 1
        $html .= '<table ' . TPL::getSnippet('sTableBackup') . '>';
481 1
        /** @var \phpbu\App\Result\Backup $backup */
482
        foreach ($backups as $backup) {
483
            if ($backup->allOk()) {
484 3
                $color  = TPL::getSnippet('cStatusOK');
485 3
                $status = 'OK';
486 3
            } elseif ($backup->okButSkipsOrFails()) {
487 3
                $color  = TPL::getSnippet('cStatusWARN');
488 3
                $status = 'WARNING';
489 3
            } else {
490 3
                $color  = TPL::getSnippet('cStatusFAIL');
491 3
                $status = 'FAILURE';
492 3
            }
493 3
            $html .= '<tr>' .
494 3
                      '<td ' . sprintf(TPL::getSnippet('sTableBackupStatusColumn'), $color) . ' colspan="4">' .
495
                      sprintf('backup <em>%s</em>', $backup->getName()) .
496
                      ' <span ' . TPL::getSnippet('sTableBackupStatusText') . '>' . $status . '</span>' .
497 3
                      '</td>' .
498 3
                     '</tr>' .
499 3
                     '<tr>' .
500
                      '<td ' . TPL::getSnippet('sRowHead') . '>&nbsp;</td>' .
501 3
                      '<td ' . TPL::getSnippet('sRowHead') . ' align="right">executed</td>' .
502
                      '<td ' . TPL::getSnippet('sRowHead') . ' align="right">skipped</td>' .
503
                      '<td ' . TPL::getSnippet('sRowHead') . ' align="right">failed</td>' .
504 3
                     '</tr>';
505 3
506 3
            $html .= '<tr>' .
507 3
                      '<td ' . TPL::getSnippet('sRowCheck') . '>checks</td>' .
508 3
                      '<td ' . TPL::getSnippet('sRowCheck') . ' align="right">' .
509 3
                        $backup->checkCount() . '
510 3
                       </td>' .
511 3
                      '<td ' . TPL::getSnippet('sRowCheck') . ' align="right">
512 3
                        &nbsp;
513 3
                       </td>' .
514 3
                      '<td ' . TPL::getSnippet('sRowCheck') . ' align="right">' .
515 3
                        $backup->checkCountFailed() .
516 3
                      '</td>' .
517 3
                     '</tr>' .
518 3
                     '<tr>' .
519 3
                      '<td ' . TPL::getSnippet('sRowCrypt') . '>crypts</td>' .
520 3
                      '<td ' . TPL::getSnippet('sRowCrypt') . ' align="right">' .
521 3
                        $backup->cryptCount() .
522 3
                      '</td>' .
523 3
                      '<td ' . TPL::getSnippet('sRowCrypt') . ' align="right">' .
524 3
                        $backup->cryptCountSkipped() .
525 3
                      '</td>' .
526 3
                      '<td ' . TPL::getSnippet('sRowCrypt') . ' align="right">' .
527 3
                        $backup->cryptCountFailed() .
528 3
                      '</td>' .
529 3
                     '</tr>' .
530 3
                     '<tr>' .
531 3
                      '<td ' . TPL::getSnippet('sRowSync') . '>syncs</td>' .
532 3
                      '<td ' . TPL::getSnippet('sRowSync') . ' align="right">' .
533 3
                        $backup->syncCount() . '</td>' .
534 3
                      '<td ' . TPL::getSnippet('sRowSync') . ' align="right">' .
535 3
                        $backup->syncCountSkipped() .
536 3
                      '</td>' .
537 3
                      '<td ' . TPL::getSnippet('sRowSync') . ' align="right">' .
538 3
                        $backup->syncCountFailed() .
539 3
                      '</td>' .
540 3
                     '</tr>' .
541 3
                     '<tr>' .
542 3
                      '<td ' . TPL::getSnippet('sRowCleanup') . '>cleanups</td>' .
543
                      '<td ' . TPL::getSnippet('sRowCleanup') . ' align="right">' .
544 3
                        $backup->cleanupCount() .
545
                      '</td>' .
546 4
                      '<td ' . TPL::getSnippet('sRowCleanup') . ' align="right">' .
547
                        $backup->cleanupCountSkipped() .
548
                      '</td>' .
549
                      '<td ' . TPL::getSnippet('sRowCleanup') . ' align="right">' .
550
                        $backup->cleanupCountFailed() .
551
                      '</td>' .
552
                     '</tr>';
553
        }
554
        $html .= '</table>';
555 4
556
        return $html;
557 4
    }
558 4
559
    /**
560
     * Return mail body footer
561
     *
562
     * @return string
563
     * @throws \phpbu\App\Exception
564
     */
565
    protected function getFooterHtml()
566
    {
567
        return '<p ' . TPL::getSnippet('sStats') . '>' . Statistics::resourceUsage() . '</p>' .
568
               '</td></tr></table>';
569
    }
570
}
571