Completed
Push — master ( 46f3eb...7c230f )
by Mathieu
02:46 queued 40s
created

Email::logSend()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 36
rs 9.344
c 0
b 0
f 0
cc 4
nc 6
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Charcoal\Email;
6
7
use Charcoal\Email\Services\Tracker;
8
use Exception;
9
use InvalidArgumentException;
10
11
// From 'psr/log' (PSR-3)
12
use Psr\Log\LoggerAwareInterface;
13
use Psr\Log\LoggerAwareTrait;
14
15
// From 'phpmailer/phpmailer'
16
use PHPMailer\PHPMailer\PHPMailer;
17
18
// From 'locomotivemtl/charcoal-config'
19
use Charcoal\Config\AbstractEntity;
20
use Charcoal\Config\ConfigurableInterface;
21
use Charcoal\Config\ConfigurableTrait;
22
23
// From 'locomotivemtl/charcoal-factory'
24
use Charcoal\Factory\FactoryInterface;
25
26
// From 'locomotivemtl/charcoal-view'
27
use Charcoal\View\GenericView;
28
use Charcoal\View\ViewableInterface;
29
use Charcoal\View\ViewableTrait;
30
31
// From 'locomotivemtl/charcoal-queue'
32
use Charcoal\Queue\QueueableInterface;
33
use Charcoal\Queue\QueueableTrait;
34
35
/**
36
 * Default implementation of the `EmailInterface`.
37
 */
38
class Email extends AbstractEntity implements
39
    ConfigurableInterface,
40
    EmailInterface,
41
    LoggerAwareInterface,
42
    QueueableInterface,
43
    ViewableInterface
44
{
45
    use ConfigurableTrait;
46
    use LoggerAwareTrait;
47
    use QueueableTrait;
48
    use ViewableTrait;
49
    use EmailAwareTrait;
50
51
    /**
52
     * The campaign ID.
53
     *
54
     * @var string
55
     */
56
    private $campaign;
57
58
    /**
59
     * The recipient email address(es).
60
     *
61
     * @var array
62
     */
63
    private $to = [];
64
65
    /**
66
     * The CC recipient email address(es).
67
     *
68
     * @var array
69
     */
70
    private $cc = [];
71
72
    /**
73
     * The BCC recipient email address(es).
74
     *
75
     * @var array
76
     */
77
    private $bcc = [];
78
79
    /**
80
     * The sender's email address.
81
     *
82
     * @var string
83
     */
84
    private $from;
85
86
    /**
87
     * The email address to reply to the message.
88
     *
89
     * @var string
90
     */
91
    private $replyTo;
92
93
    /**
94
     * The email subject.
95
     *
96
     * @var string
97
     */
98
    private $subject;
99
100
    /**
101
     * The HTML message body.
102
     *
103
     * @var string
104
     */
105
    private $msgHtml;
106
107
    /**
108
     * The plain-text message body.
109
     *
110
     * @var string
111
     */
112
    private $msgTxt;
113
114
    /**
115
     * @var array
116
     */
117
    private $attachments = [];
118
119
    /**
120
     * Whether the email should be logged.
121
     *
122
     * @var boolean
123
     */
124
    private $logEnabled;
125
126
    /**
127
     * Whether the email should be tracked.
128
     *
129
     * @var boolean
130
     */
131
    private $trackOpenEnabled;
132
133
    /**
134
     * @var boolean
135
     */
136
    private $trackLinksEnabled;
137
138
    /**
139
     * The data to pass onto the view controller.
140
     *
141
     * @var array
142
     */
143
    private $templateData = [];
144
145
    /**
146
     * @var PHPMailer
147
     */
148
    private $phpMailer;
149
150
    /**
151
     * @var FactoryInterface
152
     */
153
    private $templateFactory;
154
155
    /**
156
     * @var FactoryInterface
157
     */
158
    private $queueItemFactory;
159
160
    /**
161
     * @var FactoryInterface
162
     */
163
    private $logFactory;
164
165
    /**
166
     * @var Tracker
167
     */
168
    private $tracker;
169
170
    /**
171
     * Construct a new Email object with the given dependencies.
172
     *
173
     * - `logger` a PSR-3 logger.
174
     * - `view` a charcoal view for template rendering.
175
     * - `config` a charcoal config containing email settings.
176
     * - `template_factory` a charcoal model factory to create templates.
177
     * - `queue_item_factory` a charcoal model factory to create queue item.
178
     * - `log_factory` a charcoal model factory to create email logs.
179
     *
180
     * @param array $data Dependencies and settings.
181
     */
182
    public function __construct(array $data)
183
    {
184
        $this->phpMailer = new PHPMailer(true);
185
        $this->setLogger($data['logger']);
186
        $this->setView($data['view']);
187
        $this->setConfig($data['config']);
188
        $this->setTemplateFactory($data['template_factory']);
189
        $this->setQueueItemFactory($data['queue_item_factory']);
190
        $this->setLogFactory($data['log_factory']);
191
        $this->setTracker($data['tracker']);
192
    }
193
194
    /**
195
     * Set the campaign ID.
196
     *
197
     * @param  string $campaign The campaign identifier.
198
     * @return self
199
     */
200
    public function setCampaign(string $campaign)
201
    {
202
        $this->campaign = $campaign;
203
        return $this;
204
    }
205
206
    /**
207
     * Get the campaign identifier.
208
     *
209
     * If it has not been explicitely set, it will be auto-generated (with uniqid).
210
     *
211
     * @return string
212
     */
213
    public function campaign()
214
    {
215
        if ($this->campaign === null) {
216
            $this->campaign = $this->generateCampaign();
217
        }
218
        return $this->campaign;
219
    }
220
221
    /**
222
     * Set the recipient email address(es).
223
     *
224
     * @param string|array $email The recipient email address(es).
225
     * @throws InvalidArgumentException If the email address is invalid.
226
     * @return self
227
     */
228 View Code Duplication
    public function setTo($email)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
229
    {
230
        if (is_string($email)) {
231
            $email = [ $email ];
232
        }
233
234
        if (!is_array($email)) {
235
            throw new InvalidArgumentException(
236
                'Must be an array of recipients.'
237
            );
238
        }
239
240
        $this->to = [];
241
242
        // At this point, `$email` can be an _email array_ or an _array of emails_...
243
        if (isset($email['email'])) {
244
            // Means we're not dealing with multiple emails
245
            $this->addTo($email);
246
        } else {
247
            foreach ($email as $recipient) {
248
                $this->addTo($recipient);
249
            }
250
        }
251
252
        return $this;
253
    }
254
255
    /**
256
     * Add a recipient email address.
257
     *
258
     * @param  mixed $email The recipient email address to add.
259
     * @throws InvalidArgumentException If the email address is invalid.
260
     * @return self
261
     */
262
    public function addTo($email)
263
    {
264
        $this->to[] = $this->parseEmail($email);
265
        return $this;
266
    }
267
268
    /**
269
     * Get the recipient's email addresses.
270
     *
271
     * @return string[]
272
     */
273
    public function to()
274
    {
275
        return $this->to;
276
    }
277
278
    /**
279
     * Set the carbon copy (CC) recipient email address(es).
280
     *
281
     * @param string|array $email The CC recipient email address(es).
282
     * @throws InvalidArgumentException If the email address is invalid.
283
     * @return self
284
     */
285 View Code Duplication
    public function setCc($email)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
286
    {
287
        if (is_string($email)) {
288
            $email = [ $email ];
289
        }
290
291
        if (!is_array($email)) {
292
            throw new InvalidArgumentException(
293
                'Must be an array of CC recipients.'
294
            );
295
        }
296
297
        $this->cc = [];
298
299
        // At this point, `$email` can be an _email array_ or an _array of emails_...
300
        if (isset($email['email'])) {
301
            // Means we're not dealing with multiple emails
302
            $this->addCc($email);
303
        } else {
304
            foreach ($email as $recipient) {
305
                $this->addCc($recipient);
306
            }
307
        }
308
309
        return $this;
310
    }
311
312
    /**
313
     * Add a CC recipient email address.
314
     *
315
     * @param mixed $email The CC recipient email address to add.
316
     * @throws InvalidArgumentException If the email address is invalid.
317
     * @return self
318
     */
319
    public function addCc($email)
320
    {
321
        $this->cc[] = $this->parseEmail($email);
322
        return $this;
323
    }
324
325
    /**
326
     * Get the CC recipient's email address.
327
     *
328
     * @return string[]
329
     */
330
    public function cc()
331
    {
332
        return $this->cc;
333
    }
334
335
    /**
336
     * Set the blind carbon copy (BCC) recipient email address(es).
337
     *
338
     * @param string|array $email The BCC recipient email address(es).
339
     * @throws InvalidArgumentException If the email address is invalid.
340
     * @return self
341
     */
342 View Code Duplication
    public function setBcc($email)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
343
    {
344
        if (is_string($email)) {
345
            // Means we have a straight email
346
            $email = [ $email ];
347
        }
348
349
        if (!is_array($email)) {
350
            throw new InvalidArgumentException(
351
                'Must be an array of BCC recipients.'
352
            );
353
        }
354
355
        $this->bcc = [];
356
357
        // At this point, `$email` can be an _email array_ or an _array of emails_...
358
        if (isset($email['email'])) {
359
            // Means we're not dealing with multiple emails
360
            $this->addBcc($email);
361
        } else {
362
            foreach ($email as $recipient) {
363
                $this->addBcc($recipient);
364
            }
365
        }
366
367
        return $this;
368
    }
369
370
    /**
371
     * Add a BCC recipient email address.
372
     *
373
     * @param mixed $email The BCC recipient email address to add.
374
     * @throws InvalidArgumentException If the email address is invalid.
375
     * @return self
376
     */
377
    public function addBcc($email)
378
    {
379
        $this->bcc[] = $this->parseEmail($email);
380
        return $this;
381
    }
382
383
    /**
384
     * Get the BCC recipient's email address.
385
     *
386
     * @return string[]
387
     */
388
    public function bcc()
389
    {
390
        return $this->bcc;
391
    }
392
393
    /**
394
     * Set the sender's email address.
395
     *
396
     * @param  string|array $email An email address.
397
     * @throws InvalidArgumentException If the email is not a string or an array.
398
     * @return self
399
     * @todo   Implement optional "Sender" field.
400
     */
401
    public function setFrom($email)
402
    {
403
        $this->from = $this->parseEmail($email);
404
        return $this;
405
    }
406
407
    /**
408
     * Get the sender's email address.
409
     *
410
     * @return string
411
     */
412
    public function from()
413
    {
414
        if ($this->from === null) {
415
            $this->setFrom($this->config()->defaultFrom());
416
        }
417
        return $this->from;
418
    }
419
420
    /**
421
     * Set email address to reply to the message.
422
     *
423
     * @param  mixed $email The sender's "Reply-To" email address.
424
     * @throws InvalidArgumentException If the email is not a string or an array.
425
     * @return self
426
     */
427
    public function setReplyTo($email)
428
    {
429
        $this->replyTo = $this->parseEmail($email);
430
        return $this;
431
    }
432
433
    /**
434
     * Get email address to reply to the message.
435
     *
436
     * @return string
437
     */
438
    public function replyTo()
439
    {
440
        if ($this->replyTo === null) {
441
            $this->replyTo = $this->config()->defaultReplyTo();
442
        }
443
        return $this->replyTo;
444
    }
445
446
    /**
447
     * Set the email subject.
448
     *
449
     * @param  string $subject The email subject.
450
     * @return self
451
     */
452
    public function setSubject(string $subject)
453
    {
454
        $this->subject = $subject;
455
        return $this;
456
    }
457
458
    /**
459
     * Get the email subject.
460
     *
461
     * @return string The emails' subject.
462
     */
463
    public function subject(): string
464
    {
465
        return $this->subject;
466
    }
467
468
    /**
469
     * Set the email's HTML message body.
470
     *
471
     * @param  string $body The HTML message body.
472
     * @return self
473
     */
474
    public function setMsgHtml(string $body)
475
    {
476
        $this->msgHtml = $body;
477
        return $this;
478
    }
479
480
    /**
481
     * Get the email's HTML message body.
482
     *
483
     * If the message is not explitely set, it will be
484
     * auto-generated from a template view.
485
     *
486
     * @return string
487
     */
488
    public function msgHtml(): string
489
    {
490
        if ($this->msgHtml === null) {
491
            $this->msgHtml = $this->generateMsgHtml();
492
        }
493
        return $this->msgHtml;
494
    }
495
496
    /**
497
     * Set the email's plain-text message body.
498
     *
499
     * @param string $body The message's text body.
500
     * @return self
501
     */
502
    public function setMsgTxt(string $body)
503
    {
504
        $this->msgTxt = $body;
505
        return $this;
506
    }
507
508
    /**
509
     * Get the email's plain-text message body.
510
     *
511
     * If the plain-text message is not explitely set,
512
     * it will be auto-generated from the HTML message.
513
     *
514
     * @return string
515
     */
516
    public function msgTxt(): string
517
    {
518
        if ($this->msgTxt === null) {
519
            $this->msgTxt = $this->stripHtml($this->msgHtml());
520
        }
521
        return $this->msgTxt;
522
    }
523
524
    /**
525
     * Set the email's attachments.
526
     *
527
     * @param  array $attachments The file attachments.
528
     * @return self
529
     */
530
    public function setAttachments(array $attachments)
531
    {
532
        foreach ($attachments as $att) {
533
            $this->addAttachment($att);
534
        }
535
        return $this;
536
    }
537
538
    /**
539
     * Add an attachment to the email.
540
     *
541
     * @param  mixed $attachment A single file attachment.
542
     * @return self
543
     */
544
    public function addAttachment($attachment)
545
    {
546
        $this->attachments[] = $attachment;
547
        return $this;
548
    }
549
550
    /**
551
     * Get the email's attachments.
552
     *
553
     * @return array
554
     */
555
    public function attachments()
556
    {
557
        return $this->attachments;
558
    }
559
560
    /**
561
     * Enable or disable logging for this particular email.
562
     *
563
     * @param  boolean $log The log-enabled flag.
564
     * @return self
565
     */
566
    public function setLogEnabled($log)
567
    {
568
        $this->logEnabled = !!$log;
569
        return $this;
570
    }
571
572
    /**
573
     * Determine if logging is enabled for this particular email.
574
     *
575
     * @return boolean
576
     */
577
    public function logEnabled(): bool
578
    {
579
        if ($this->logEnabled === null) {
580
            $this->logEnabled = $this->config()->defaultLogEnabled();
581
        }
582
        return $this->logEnabled;
583
    }
584
585
    /**
586
     * Enable or disable email open tracking for this particular email.
587
     *
588
     * @param boolean $track The track flag.
589
     * @return self
590
     */
591
    public function setTrackOpenEnabled($track)
592
    {
593
        $this->trackOpenEnabled = !!$track;
594
        return $this;
595
    }
596
597
    /**
598
     * Determine if email open tracking is enabled for this particular email.
599
     *
600
     * @return boolean
601
     */
602
    public function trackOpenEnabled(): bool
603
    {
604
        if ($this->trackOpenEnabled === null) {
605
            $this->trackOpenEnabled = $this->config()->defaultTrackOpenEnabled();
606
        }
607
        return $this->trackOpenEnabled;
608
    }
609
610
    /**
611
     * Enable or disable email links tracking for this particular email.
612
     *
613
     * @param boolean $track The track flag.
614
     * @return self
615
     */
616
    public function setTrackLinksEnabled($track)
617
    {
618
        $this->trackLinksEnabled = !!$track;
619
        return $this;
620
    }
621
622
    /**
623
     * Determine if email links tracking is enabled for this particular email.
624
     *
625
     * @return boolean
626
     */
627
    public function trackLinksEnabled(): bool
628
    {
629
        if ($this->trackLinksEnabled === null) {
630
            $this->trackLinksEnabled = $this->config()->defaultTrackLinksEnabled();
631
        }
632
        return $this->trackLinksEnabled;
633
    }
634
635
636
    /**
637
     * Send the email to all recipients
638
     *
639
     * @return boolean Success / Failure.
640
     * @todo Implement methods and property for toggling rich-text vs. plain-text
641
     *       emails (`$mail->isHTML(true)`).
642
     */
643
    public function send(): bool
644
    {
645
        $this->logger->debug(
646
            'Attempting to send an email',
647
            $this->to()
648
        );
649
650
        $mail = $this->phpMailer;
651
652
        try {
653
            $this->setSmtpOptions($mail);
654
655
            $mail->CharSet = 'UTF-8';
656
657
            // Setting reply-to field, if required.
658
            $replyTo = $this->replyTo();
659
            if ($replyTo) {
660
                $replyArr = $this->emailToArray($replyTo);
661
                $mail->addReplyTo($replyArr['email'], $replyArr['name']);
662
            }
663
664
            // Setting from (sender) field.
665
            $from = $this->from();
666
            $fromArr = $this->emailToArray($from);
667
            $mail->setFrom($fromArr['email'], $fromArr['name']);
668
669
            // Setting to (recipients) field(s).
670
            $to = $this->to();
671
            foreach ($to as $recipient) {
672
                $toArr = $this->emailToArray($recipient);
673
                $mail->addAddress($toArr['email'], $toArr['name']);
674
            }
675
676
            // Setting cc (carbon-copy) field(s).
677
            $cc = $this->cc();
678
            foreach ($cc as $ccRecipient) {
679
                $ccArr = $this->emailToArray($ccRecipient);
680
                $mail->addCC($ccArr['email'], $ccArr['name']);
681
            }
682
683
            // Setting bcc (black-carbon-copy) field(s).
684
            $bcc = $this->bcc();
685
            foreach ($bcc as $bccRecipient) {
686
                $bccArr = $this->emailToArray($bccRecipient);
687
                $mail->addBCC($bccArr['email'], $bccArr['name']);
688
            }
689
690
            // Setting attachment(s), if required.
691
            $attachments = $this->attachments();
692
            foreach ($attachments as $att) {
693
                $mail->addAttachment($att);
694
            }
695
696
            $mail->isHTML(true);
697
698
            $logId = uniqid();
699
700
            if ($this->trackOpenEnabled() === true) {
701
                $this->tracker->addOpenTrackingImage($this, $logId);
702
            }
703
            if ($this->trackLinksEnabled() === true) {
704
                $this->tracker->replaceLinksWithTracker($this, $logId);
705
            }
706
707
            $mail->Subject = $this->subject();
708
            $mail->Body    = $this->msgHtml();
709
            $mail->AltBody = $this->msgTxt();
710
711
            $ret = $mail->send();
712
713
            if ($this->logEnabled() === true) {
714
                $this->logSend($ret, $logId, $mail);
715
            }
716
        } catch (Exception $e) {
717
            $ret = false;
718
            $this->logger->error(
719
                sprintf('Error sending email: %s', $e->getMessage())
720
            );
721
        }
722
723
        return $ret;
724
    }
725
726
    /**
727
     * Set the SMTP's options for PHPMailer.
728
     *
729
     * @param PHPMailer $mail The PHPMailer to setup.
730
     * @return void
731
     */
732
    protected function setSmtpOptions(PHPMailer $mail)
733
    {
734
        $config = $this->config();
735
        if (!$config['smtp']) {
736
            return;
737
        }
738
739
        $this->logger->debug(
740
            sprintf('Using SMTP "%s" server to send email', $config['smtp_hostname'])
741
        );
742
743
        $mail->isSMTP();
744
        $mail->Host       = $config['smtp_hostname'];
745
        $mail->Port       = $config['smtp_port'];
746
        $mail->SMTPAuth   = $config['smtp_auth'];
747
        $mail->Username   = $config['smtp_username'];
748
        $mail->Password   = $config['smtp_password'];
749
        $mail->SMTPSecure = $config['smtp_security'];
750
    }
751
752
    /**
753
     * Enqueue the email for each recipient.
754
     *
755
     * @param mixed $ts A date/time to initiate the queue processing.
756
     * @return self
757
     */
758
    public function queue($ts = null)
759
    {
760
        $recipients = $this->to();
761
        $author     = $this->from();
762
        $subject    = $this->subject();
763
        $msgHtml    = $this->msgHtml();
764
        $msgTxt     = $this->msgTxt();
765
        $campaign   = $this->campaign();
766
        $queueId    = $this->queueId();
767
768
        foreach ($recipients as $to) {
769
            if (is_string($to) && !empty($to)) {
770
                $queueItem = $this->queueItemFactory()->create(EmailQueueItem::class);
771
772
                $queueItem->setTo($to);
773
                $queueItem->setFrom($author);
774
                $queueItem->setSubject($subject);
775
                $queueItem->setMsgHtml($msgHtml);
776
                $queueItem->setMsgTxt($msgTxt);
777
                $queueItem->setCampaign($campaign);
778
                $queueItem->setProcessingDate($ts);
779
                $queueItem->setQueueId($queueId);
780
781
                $res = $queueItem->save();
0 ignored issues
show
Unused Code introduced by
$res is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
782
            } else {
783
                $this->logger->warning('Could not queue email, null or empty value');
784
            }
785
        }
786
787
        return $this;
788
    }
789
790
    /**
791
     * Set the template data for the view.
792
     *
793
     * @param array $data The template data.
794
     * @return Email Chainable
795
     */
796
    public function setTemplateData(array $data)
797
    {
798
        $this->templateData = $data;
799
        return $this;
800
    }
801
802
    /**
803
     * Get the template data for the view.
804
     *
805
     * @return array
806
     */
807
    public function templateData(): array
808
    {
809
        return $this->templateData;
810
    }
811
812
    /**
813
     * Get the custom view controller for rendering
814
     * the email's HTML message.
815
     *
816
     * Unlike typical `ViewableInterface` objects, the view controller is not
817
     * the email itself but an external "email" template.
818
     *
819
     * @see    ViewableInterface::viewController()
820
     * @return \Charcoal\App\Template\TemplateInterface|array
821
     */
822
    public function viewController()
823
    {
824
        $templateIdent = $this->templateIdent();
825
826
        if (!$templateIdent) {
827
            return [];
828
        }
829
830
        $templateFactory = clone($this->templateFactory());
831
        $templateFactory->setDefaultClass(GenericEmailTemplate::class);
0 ignored issues
show
Bug introduced by
The method setDefaultClass() does not exist on Charcoal\Factory\FactoryInterface. Did you maybe mean defaultClass()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
832
        $template = $templateFactory->create($templateIdent);
833
834
        $template->setData($this->templateData());
835
836
        return $template;
837
    }
838
839
    /**
840
     * @param FactoryInterface $factory The factory to use to create email template objects.
841
     * @return Email Chainable
842
     */
843
    protected function setTemplateFactory(FactoryInterface $factory)
844
    {
845
        $this->templateFactory = $factory;
846
        return $this;
847
    }
848
849
    /**
850
     * @return FactoryInterface
851
     */
852
    protected function templateFactory(): FactoryInterface
853
    {
854
        return $this->templateFactory;
855
    }
856
857
    /**
858
     * @param FactoryInterface $factory The factory to use to create email queue item objects.
859
     * @return Email Chainable
860
     */
861
    protected function setQueueItemFactory(FactoryInterface $factory)
862
    {
863
        $this->queueItemFactory = $factory;
864
        return $this;
865
    }
866
867
    /**
868
     * @return FactoryInterface
869
     */
870
    protected function queueItemFactory(): FactoryInterface
871
    {
872
        return $this->queueItemFactory;
873
    }
874
875
    /**
876
     * @param FactoryInterface $factory The factory to use to create log objects.
877
     * @return Email Chainable
878
     */
879
    protected function setLogFactory(FactoryInterface $factory)
880
    {
881
        $this->logFactory = $factory;
882
        return $this;
883
    }
884
885
    /**
886
     * @return FactoryInterface
887
     */
888
    protected function logFactory(): FactoryInterface
889
    {
890
        return $this->logFactory;
891
    }
892
893
    /**
894
     * @param Tracker $tracker Tracker service.
895
     * @return void
896
     */
897
    public function setTracker(Tracker $tracker)
898
    {
899
        $this->tracker = $tracker;
900
    }
901
902
    /**
903
     * Get the email's HTML message from the template, if applicable.
904
     *
905
     * @see    ViewableInterface::renderTemplate()
906
     * @return string
907
     */
908
    protected function generateMsgHtml(): string
909
    {
910
        $templateIdent = $this->templateIdent();
911
912
        if (!$templateIdent) {
913
            $message = '';
914
        } else {
915
            $message = $this->renderTemplate($templateIdent);
916
        }
917
918
        return $message;
919
    }
920
921
    /**
922
     * Generates a unique identifier ideal for a campaign ID.
923
     *
924
     * @return string
925
     */
926
    protected function generateCampaign(): string
927
    {
928
        return uniqid();
929
    }
930
931
    /**
932
     * Allow an object to define how the key getter are called.
933
     *
934
     * @param string $key The key to get the getter from.
935
     * @return string The getter method name, for a given key.
936
     */
937
    protected function getter(string $key): string
938
    {
939
        $getter = $key;
940
        return $this->camelize($getter);
941
    }
942
943
    /**
944
     * Allow an object to define how the key setter are called.
945
     *
946
     * @param string $key The key to get the setter from.
947
     * @return string The setter method name, for a given key.
948
     */
949
    protected function setter(string $key): string
950
    {
951
        $setter = 'set_'.$key;
952
        return $this->camelize($setter);
953
    }
954
955
    /**
956
     * Convert an HTML string to plain-text.
957
     *
958
     * @param string $html The HTML string to convert.
959
     * @return string The resulting plain-text string.
960
     */
961
    protected function stripHtml(string $html): string
962
    {
963
        $str = html_entity_decode($html);
964
965
        // Strip HTML (Replace br with newline, remove "invisible" tags and strip other tags)
966
        $str = preg_replace('#<br[^>]*?>#siu', "\n", $str);
967
        $str = preg_replace(
968
            [
969
                '#<applet[^>]*?.*?</applet>#siu',
970
                '#<embed[^>]*?.*?</embed>#siu',
971
                '#<head[^>]*?>.*?</head>#siu',
972
                '#<noframes[^>]*?.*?</noframes>#siu',
973
                '#<noscript[^>]*?.*?</noscript>#siu',
974
                '#<noembed[^>]*?.*?</noembed>#siu',
975
                '#<object[^>]*?.*?</object>#siu',
976
                '#<script[^>]*?.*?</script>#siu',
977
                '#<style[^>]*?>.*?</style>#siu'
978
            ],
979
            '',
980
            $str
981
        );
982
        $str = strip_tags($str);
983
984
        // Trim whitespace
985
        $str = str_replace("\t", '', $str);
986
        $str = preg_replace('#\n\r|\r\n#', "\n", $str);
987
        $str = preg_replace('#\n{3,}#', "\n\n", $str);
988
        $str = preg_replace('/ {2,}/', ' ', $str);
989
        $str = implode("\n", array_map('trim', explode("\n", $str)));
990
        $str = trim($str)."\n";
991
        return $str;
992
    }
993
994
    /**
995
     * Log the send event for each recipient.
996
     *
997
     * @param  boolean   $result Success or failure.
998
     * @param  string    $logId  Email log id.
999
     * @param  PHPMailer $mailer The raw mailer.
1000
     * @return void
1001
     */
1002
    protected function logSend(bool $result, string $logId, PHPMailer $mailer): void
1003
    {
1004
        if (!$result) {
1005
            $this->logger->error('Email could not be sent.');
1006
        } else {
1007
            $this->logger->debug(
1008
                sprintf('Email "%s" sent successfully.', $this->subject()),
1009
                $this->to()
1010
            );
1011
        }
1012
1013
        $recipients = array_merge(
1014
            $this->to(),
1015
            $this->cc(),
1016
            $this->bcc()
1017
        );
1018
1019
        foreach ($recipients as $to) {
1020
            $log = $this->logFactory()->create(EmailLog::class);
1021
1022
            $log->setId($logId);
1023
            $log->setQueueId($this->queueId());
1024
            $log->setMessageId($mailer->getLastMessageId());
1025
            $log->setCampaign($this->campaign());
1026
            $log->setSendTs('now');
1027
            $log->setFrom($mailer->From);
1028
            $log->setTo($to);
1029
            $log->setSubject($this->subject());
1030
1031
            if (!empty($mailer->getSMTPInstance()->getError()['smtp_code'])) {
1032
                $log->setErrorCode($mailer->getSMTPInstance()->getError()['smtp_code']);
1033
            }
1034
1035
            $log->save();
1036
        }
1037
    }
1038
1039
    /**
1040
     * Temporary hack to fulfills the Configurable Interface.
1041
     *
1042
     * @return EmailConfig
1043
     */
1044
    public function createConfig()
1045
    {
1046
        // This should really be avoided.
1047
        $this->logger->warning('AbstractEmail::createConfig() was called, but should not.');
1048
        return new \Charcoal\Email\EmailConfig();
1049
    }
1050
}
1051