Passed
Pull Request — 4 (#10222)
by Steve
06:24
created

Email::__construct()   C

Complexity

Conditions 9
Paths 256

Size

Total Lines 36
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 17
nc 256
nop 7
dl 0
loc 36
rs 6.5222
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Control\Email;
4
5
use DateTime;
6
use RuntimeException;
7
use Egulias\EmailValidator\EmailValidator;
8
use Egulias\EmailValidator\Validation\RFCValidation;
9
use SilverStripe\Control\Director;
10
use SilverStripe\Control\HTTP;
11
use SilverStripe\Core\Convert;
12
use SilverStripe\Core\Environment;
13
use SilverStripe\Core\Injector\Injector;
14
use SilverStripe\ORM\FieldType\DBDatetime;
15
use SilverStripe\ORM\FieldType\DBField;
16
use SilverStripe\ORM\FieldType\DBHTMLText;
17
use SilverStripe\View\Requirements;
18
use SilverStripe\View\SSViewer;
19
use SilverStripe\View\ThemeResourceLoader;
20
use SilverStripe\View\ViewableData;
21
use Swift_Message;
22
use Swift_Mime_SimpleMessage;
23
use Swift_MimePart;
24
25
/**
26
 * Class to support sending emails.
27
 */
28
class Email extends ViewableData
29
{
30
    /**
31
     * @var array
32
     * @config
33
     */
34
    private static $send_all_emails_to = [];
35
36
    /**
37
     * @var array
38
     * @config
39
     */
40
    private static $cc_all_emails_to = [];
41
42
    /**
43
     * @var array
44
     * @config
45
     */
46
    private static $bcc_all_emails_to = [];
47
48
    /**
49
     * @var array
50
     * @config
51
     */
52
    private static $send_all_emails_from = [];
53
54
    /**
55
     * This will be set in the config on a site-by-site basis
56
     * @see https://docs.silverstripe.org/en/4/developer_guides/email/#administrator-emails
57
     *
58
     * @config
59
     * @var string|array The default administrator email address or array of [email => name]
60
     */
61
    private static $admin_email = null;
62
63
    /**
64
     * @var Swift_Message
65
     */
66
    private $swiftMessage;
67
68
    /**
69
     * @var string The name of the HTML template to render the email with (without *.ss extension)
70
     */
71
    private $HTMLTemplate = null;
72
73
    /**
74
     * @var string The name of the plain text template to render the plain part of the email with
75
     */
76
    private $plainTemplate = null;
77
78
    /**
79
     * @var Swift_MimePart
80
     */
81
    private $plainPart;
82
83
    /**
84
     * @var array|ViewableData Additional data available in a template.
85
     * Used in the same way than {@link ViewableData->customize()}.
86
     */
87
    private $data = [];
88
89
    /**
90
     * @var array
91
     */
92
    private $failedRecipients = [];
93
94
    /**
95
     * Checks for RFC822-valid email format.
96
     *
97
     * @param string $address
98
     * @return boolean
99
     *
100
     * @copyright Cal Henderson <[email protected]>
101
     *    This code is licensed under a Creative Commons Attribution-ShareAlike 2.5 License
102
     *    http://creativecommons.org/licenses/by-sa/2.5/
103
     */
104
    public static function is_valid_address($address)
105
    {
106
        $validator = new EmailValidator();
107
        return $validator->isValid($address, new RFCValidation());
108
    }
109
110
    /**
111
     * Get send_all_emails_to
112
     *
113
     * @return array Keys are addresses, values are names
114
     */
115
    public static function getSendAllEmailsTo()
116
    {
117
        return static::mergeConfiguredEmails('send_all_emails_to', 'SS_SEND_ALL_EMAILS_TO');
118
    }
119
120
    /**
121
     * Get cc_all_emails_to
122
     *
123
     * @return array
124
     */
125
    public static function getCCAllEmailsTo()
126
    {
127
        return static::mergeConfiguredEmails('cc_all_emails_to', 'SS_CC_ALL_EMAILS_TO');
128
    }
129
130
    /**
131
     * Get bcc_all_emails_to
132
     *
133
     * @return array
134
     */
135
    public static function getBCCAllEmailsTo()
136
    {
137
        return static::mergeConfiguredEmails('bcc_all_emails_to', 'SS_BCC_ALL_EMAILS_TO');
138
    }
139
140
    /**
141
     * Get send_all_emails_from
142
     *
143
     * @return array
144
     */
145
    public static function getSendAllEmailsFrom()
146
    {
147
        return static::mergeConfiguredEmails('send_all_emails_from', 'SS_SEND_ALL_EMAILS_FROM');
148
    }
149
150
    /**
151
     * Normalise email list from config merged with env vars
152
     *
153
     * @param string $config Config key
154
     * @param string $env Env variable key
155
     * @return array Array of email addresses
156
     */
157
    protected static function mergeConfiguredEmails($config, $env)
158
    {
159
        // Normalise config list
160
        $normalised = [];
161
        $source = (array)static::config()->get($config);
162
        foreach ($source as $address => $name) {
163
            if ($address && !is_numeric($address)) {
164
                $normalised[$address] = $name;
165
            } elseif ($name) {
166
                $normalised[$name] = null;
167
            }
168
        }
169
        $extra = Environment::getEnv($env);
170
        if ($extra) {
171
            $normalised[$extra] = null;
172
        }
173
        return $normalised;
174
    }
175
176
    /**
177
     * Encode an email-address to protect it from spambots.
178
     * At the moment only simple string substitutions,
179
     * which are not 100% safe from email harvesting.
180
     *
181
     * @param string $email Email-address
182
     * @param string $method Method for obfuscating/encoding the address
183
     *    - 'direction': Reverse the text and then use CSS to put the text direction back to normal
184
     *    - 'visible': Simple string substitution ('@' to '[at]', '.' to '[dot], '-' to [dash])
185
     *    - 'hex': Hexadecimal URL-Encoding - useful for mailto: links
186
     * @return string
187
     */
188
    public static function obfuscate($email, $method = 'visible')
189
    {
190
        switch ($method) {
191
            case 'direction':
192
                Requirements::customCSS('span.codedirection { unicode-bidi: bidi-override; direction: rtl; }', 'codedirectionCSS');
193
194
                return '<span class="codedirection">' . strrev($email) . '</span>';
195
            case 'visible':
196
                $obfuscated = ['@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] '];
197
198
                return strtr($email, $obfuscated);
199
            case 'hex':
200
                $encoded = '';
201
                $emailLength = strlen($email);
202
                for ($x = 0; $x < $emailLength; $x++) {
203
                    $encoded .= '&#x' . bin2hex($email[$x]) . ';';
204
                }
205
206
                return $encoded;
207
            default:
208
                user_error('Email::obfuscate(): Unknown obfuscation method', E_USER_NOTICE);
209
210
                return $email;
211
        }
212
    }
213
214
    /**
215
     * Email constructor.
216
     * @param string|array|null $from
217
     * @param string|array|null $to
218
     * @param string|null $subject
219
     * @param string|null $body
220
     * @param string|array|null $cc
221
     * @param string|array|null $bcc
222
     * @param string|null $returnPath
223
     */
224
    public function __construct(
225
        $from = null,
226
        $to = null,
227
        $subject = null,
228
        $body = null,
229
        $cc = null,
230
        $bcc = null,
231
        $returnPath = null
232
    ) {
233
        if ($from) {
234
            $this->setFrom($from);
235
        }
236
        if ($to) {
237
            $this->setTo($to);
238
        }
239
        if ($subject) {
240
            $this->setSubject($subject);
241
        }
242
        if ($body) {
243
            $this->setBody($body);
244
        }
245
        if ($cc) {
246
            $this->setCC($cc);
247
        }
248
        if ($bcc) {
249
            $this->setBCC($bcc);
250
        }
251
        if ($returnPath) {
252
            $this->setReturnPath($returnPath);
253
        }
254
        // The following is a fix for PHP 8.1 SimpleMessage::getPriority() sscanf() null parameter
255
        if (!$this->getSwiftMessage()->getHeaders()->has('X-Priority')) {
256
            $this->setPriority(Swift_Mime_SimpleMessage::PRIORITY_NORMAL);
257
        }
258
259
        parent::__construct();
260
    }
261
262
    /**
263
     * @return Swift_Message
264
     */
265
    public function getSwiftMessage()
266
    {
267
        if (!$this->swiftMessage) {
268
            $this->setSwiftMessage(new Swift_Message(null, null, 'text/html', 'utf-8'));
269
        }
270
271
        return $this->swiftMessage;
272
    }
273
274
    /**
275
     * @param Swift_Message $swiftMessage
276
     *
277
     * @return $this
278
     */
279
    public function setSwiftMessage($swiftMessage)
280
    {
281
        $dateTime = new DateTime();
282
        $dateTime->setTimestamp(DBDatetime::now()->getTimestamp());
283
        $swiftMessage->setDate($dateTime);
284
        if (!$swiftMessage->getFrom()) {
285
            $swiftMessage->setFrom($this->getDefaultFrom());
286
        }
287
        $this->swiftMessage = $swiftMessage;
288
289
        return $this;
290
    }
291
292
    /**
293
     * @return string
294
     */
295
    private function getDefaultFrom(): string
296
    {
297
        // admin_email can have a string or an array config
298
        // https://docs.silverstripe.org/en/4/developer_guides/email/#administrator-emails
299
        $adminEmail = $this->config()->get('admin_email');
300
        if (is_array($adminEmail) && count($adminEmail ?? []) > 0) {
301
            $defaultFrom = array_keys($adminEmail)[0];
302
        } else {
303
            if (is_string($adminEmail)) {
304
                $defaultFrom = $adminEmail;
305
            } else {
306
                $defaultFrom = '';
307
            }
308
        }
309
        if (empty($defaultFrom)) {
310
            $host = Director::host();
311
            if (empty($host)) {
312
                throw new RuntimeException('Host not defined');
313
            }
314
            $defaultFrom = sprintf('no-reply@%s', $host);
315
        }
316
        $this->extend('updateDefaultFrom', $defaultFrom);
317
        return $defaultFrom;
318
    }
319
320
    /**
321
     * @return string[]
322
     */
323
    public function getFrom()
324
    {
325
        return $this->getSwiftMessage()->getFrom();
326
    }
327
328
    /**
329
     * @param string|array $address
330
     * @return string|array
331
     */
332
    private function sanitiseAddress($address)
333
    {
334
        if (is_array($address)) {
335
            return array_map('trim', $address ?? []);
336
        }
337
        return trim($address ?? '');
338
    }
339
340
    /**
341
     * @param string|array $address
342
     * @param string|null $name
343
     * @return $this
344
     */
345
    public function setFrom($address, $name = null)
346
    {
347
        $address = $this->sanitiseAddress($address);
348
        $this->getSwiftMessage()->setFrom($address, $name);
349
350
        return $this;
351
    }
352
353
    /**
354
     * @param string|array $address
355
     * @param string|null $name
356
     * @return $this
357
     */
358
    public function addFrom($address, $name = null)
359
    {
360
        $address = $this->sanitiseAddress($address);
361
        $this->getSwiftMessage()->addFrom($address, $name);
0 ignored issues
show
Bug introduced by
It seems like $address can also be of type array; however, parameter $address of Swift_Mime_SimpleMessage::addFrom() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

361
        $this->getSwiftMessage()->addFrom(/** @scrutinizer ignore-type */ $address, $name);
Loading history...
362
363
        return $this;
364
    }
365
366
    /**
367
     * @return string
368
     */
369
    public function getSender()
370
    {
371
        return $this->getSwiftMessage()->getSender();
372
    }
373
374
    /**
375
     * @param string $address
376
     * @param string|null $name
377
     * @return $this
378
     */
379
    public function setSender($address, $name = null)
380
    {
381
        $address = $this->sanitiseAddress($address);
382
        $this->getSwiftMessage()->setSender($address, $name);
383
384
        return $this;
385
    }
386
387
    /**
388
     * @return string
389
     */
390
    public function getReturnPath()
391
    {
392
        return $this->getSwiftMessage()->getReturnPath();
393
    }
394
395
    /**
396
     * The bounce handler address
397
     *
398
     * @param string $address Email address where bounce notifications should be sent
399
     * @return $this
400
     */
401
    public function setReturnPath($address)
402
    {
403
        $address = $this->sanitiseAddress($address);
404
        $this->getSwiftMessage()->setReturnPath($address);
405
        return $this;
406
    }
407
408
    /**
409
     * @return array
410
     */
411
    public function getTo()
412
    {
413
        return $this->getSwiftMessage()->getTo();
414
    }
415
416
    /**
417
     * Set recipient(s) of the email
418
     *
419
     * To send to many, pass an array:
420
     * ['[email protected]' => 'My Name', '[email protected]'];
421
     *
422
     * @param string|array $address The message recipient(s) - if sending to multiple, use an array of address => name
423
     * @param string|null $name The name of the recipient (if one)
424
     * @return $this
425
     */
426
    public function setTo($address, $name = null)
427
    {
428
        $address = $this->sanitiseAddress($address);
429
        $this->getSwiftMessage()->setTo($address, $name);
430
431
        return $this;
432
    }
433
434
    /**
435
     * @param string|array $address
436
     * @param string|null $name
437
     * @return $this
438
     */
439
    public function addTo($address, $name = null)
440
    {
441
        $address = $this->sanitiseAddress($address);
442
        $this->getSwiftMessage()->addTo($address, $name);
0 ignored issues
show
Bug introduced by
It seems like $address can also be of type array; however, parameter $address of Swift_Mime_SimpleMessage::addTo() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

442
        $this->getSwiftMessage()->addTo(/** @scrutinizer ignore-type */ $address, $name);
Loading history...
443
444
        return $this;
445
    }
446
447
    /**
448
     * @return array
449
     */
450
    public function getCC()
451
    {
452
        return $this->getSwiftMessage()->getCc();
453
    }
454
455
    /**
456
     * @param string|array $address
457
     * @param string|null $name
458
     * @return $this
459
     */
460
    public function setCC($address, $name = null)
461
    {
462
        $address = $this->sanitiseAddress($address);
463
        $this->getSwiftMessage()->setCc($address, $name);
464
465
        return $this;
466
    }
467
468
    /**
469
     * @param string|array $address
470
     * @param string|null $name
471
     * @return $this
472
     */
473
    public function addCC($address, $name = null)
474
    {
475
        $address = $this->sanitiseAddress($address);
476
        $this->getSwiftMessage()->addCc($address, $name);
0 ignored issues
show
Bug introduced by
It seems like $address can also be of type array; however, parameter $address of Swift_Mime_SimpleMessage::addCc() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

476
        $this->getSwiftMessage()->addCc(/** @scrutinizer ignore-type */ $address, $name);
Loading history...
477
478
        return $this;
479
    }
480
481
    /**
482
     * @return array
483
     */
484
    public function getBCC()
485
    {
486
        return $this->getSwiftMessage()->getBcc();
487
    }
488
489
    /**
490
     * @param string|array $address
491
     * @param string|null $name
492
     * @return $this
493
     */
494
    public function setBCC($address, $name = null)
495
    {
496
        $address = $this->sanitiseAddress($address);
497
        $this->getSwiftMessage()->setBcc($address, $name);
498
499
        return $this;
500
    }
501
502
    /**
503
     * @param string|array $address
504
     * @param string|null $name
505
     * @return $this
506
     */
507
    public function addBCC($address, $name = null)
508
    {
509
        $address = $this->sanitiseAddress($address);
510
        $this->getSwiftMessage()->addBcc($address, $name);
0 ignored issues
show
Bug introduced by
It seems like $address can also be of type array; however, parameter $address of Swift_Mime_SimpleMessage::addBcc() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

510
        $this->getSwiftMessage()->addBcc(/** @scrutinizer ignore-type */ $address, $name);
Loading history...
511
512
        return $this;
513
    }
514
515
    /**
516
     * @return mixed
517
     */
518
    public function getReplyTo()
519
    {
520
        return $this->getSwiftMessage()->getReplyTo();
521
    }
522
523
    /**
524
     * @param string|array $address
525
     * @param string|null $name
526
     * @return $this
527
     */
528
    public function setReplyTo($address, $name = null)
529
    {
530
        $address = $this->sanitiseAddress($address);
531
        $this->getSwiftMessage()->setReplyTo($address, $name);
532
533
        return $this;
534
    }
535
536
    /**
537
     * @param string|array $address
538
     * @param string|null $name
539
     * @return $this
540
     */
541
    public function addReplyTo($address, $name = null)
542
    {
543
        $address = $this->sanitiseAddress($address);
544
        $this->getSwiftMessage()->addReplyTo($address, $name);
0 ignored issues
show
Bug introduced by
It seems like $address can also be of type array; however, parameter $address of Swift_Mime_SimpleMessage::addReplyTo() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

544
        $this->getSwiftMessage()->addReplyTo(/** @scrutinizer ignore-type */ $address, $name);
Loading history...
545
546
        return $this;
547
    }
548
549
    /**
550
     * @return string
551
     */
552
    public function getSubject()
553
    {
554
        return $this->getSwiftMessage()->getSubject();
555
    }
556
557
    /**
558
     * @param string $subject The Subject line for the email
559
     * @return $this
560
     */
561
    public function setSubject($subject)
562
    {
563
        $this->getSwiftMessage()->setSubject($subject);
564
565
        return $this;
566
    }
567
568
    /**
569
     * @return int
570
     */
571
    public function getPriority()
572
    {
573
        return $this->getSwiftMessage()->getPriority();
574
    }
575
576
    /**
577
     * @param int $priority
578
     * @return $this
579
     */
580
    public function setPriority($priority)
581
    {
582
        $this->getSwiftMessage()->setPriority($priority);
583
584
        return $this;
585
    }
586
587
    /**
588
     * @param string $path Path to file
589
     * @param string $alias An override for the name of the file
590
     * @param string $mime The mime type for the attachment
591
     * @return $this
592
     */
593
    public function addAttachment($path, $alias = null, $mime = null)
594
    {
595
        $attachment = \Swift_Attachment::fromPath($path);
596
        if ($alias) {
597
            $attachment->setFilename($alias);
598
        }
599
        if ($mime) {
600
            $attachment->setContentType($mime);
601
        }
602
        $this->getSwiftMessage()->attach($attachment);
603
604
        return $this;
605
    }
606
607
    /**
608
     * @param string $data
609
     * @param string $name
610
     * @param string $mime
611
     * @return $this
612
     */
613
    public function addAttachmentFromData($data, $name, $mime = null)
614
    {
615
        $attachment = new \Swift_Attachment($data, $name);
616
        if ($mime) {
617
            $attachment->setContentType($mime);
618
        }
619
        $this->getSwiftMessage()->attach($attachment);
620
621
        return $this;
622
    }
623
624
    /**
625
     * @return array|ViewableData The template data
626
     */
627
    public function getData()
628
    {
629
        return $this->data;
630
    }
631
632
    /**
633
     * @param array|ViewableData $data The template data to set
634
     * @return $this
635
     */
636
    public function setData($data)
637
    {
638
        $this->data = $data;
639
        $this->invalidateBody();
640
641
        return $this;
642
    }
643
644
    /**
645
     * @param string|array $name The data name to add or array to names => value
646
     * @param string|null $value The value of the data to add
647
     * @return $this
648
     */
649
    public function addData($name, $value = null)
650
    {
651
        if (is_array($name)) {
652
            $this->data = array_merge($this->data, $name);
0 ignored issues
show
Bug introduced by
It seems like $this->data can also be of type SilverStripe\View\ViewableData; however, parameter $arrays of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

652
            $this->data = array_merge(/** @scrutinizer ignore-type */ $this->data, $name);
Loading history...
653
        } elseif (is_array($this->data)) {
654
            $this->data[$name] = $value;
655
        } else {
656
            $this->data->$name = $value;
657
        }
658
659
        $this->invalidateBody();
660
661
        return $this;
662
    }
663
664
    /**
665
     * Remove a datum from the message
666
     *
667
     * @param string $name
668
     * @return $this
669
     */
670
    public function removeData($name)
671
    {
672
        if (is_array($this->data)) {
673
            unset($this->data[$name]);
674
        } else {
675
            $this->data->$name = null;
676
        }
677
678
        $this->invalidateBody();
679
680
        return $this;
681
    }
682
683
    /**
684
     * @return string
685
     */
686
    public function getBody()
687
    {
688
        return $this->getSwiftMessage()->getBody();
689
    }
690
691
    /**
692
     * @param string $body The email body
693
     * @return $this
694
     */
695
    public function setBody($body)
696
    {
697
        $plainPart = $this->findPlainPart();
698
        if ($plainPart) {
699
            $this->getSwiftMessage()->detach($plainPart);
700
        }
701
        unset($plainPart);
702
703
        $body = HTTP::absoluteURLs($body);
704
        $this->getSwiftMessage()->setBody($body);
705
706
        return $this;
707
    }
708
709
    /**
710
     * @return $this
711
     */
712
    public function invalidateBody()
713
    {
714
        $this->setBody(null);
715
716
        return $this;
717
    }
718
719
    /**
720
     * @return string The base URL for the email
721
     */
722
    public function BaseURL()
723
    {
724
        return Director::absoluteBaseURL();
725
    }
726
727
    /**
728
     * Debugging help
729
     *
730
     * @return string Debug info
731
     */
732
    public function debug()
733
    {
734
        $this->render();
735
736
        $class = static::class;
737
        return "<h2>Email template {$class}:</h2>\n" . '<pre>' . $this->getSwiftMessage()->toString() . '</pre>';
738
    }
739
740
    /**
741
     * @return string
742
     */
743
    public function getHTMLTemplate()
744
    {
745
        if ($this->HTMLTemplate) {
746
            return $this->HTMLTemplate;
747
        }
748
749
        return ThemeResourceLoader::inst()->findTemplate(
750
            SSViewer::get_templates_by_class(static::class, '', self::class),
751
            SSViewer::get_themes()
752
        );
753
    }
754
755
    /**
756
     * Set the template to render the email with
757
     *
758
     * @param string $template
759
     * @return $this
760
     */
761
    public function setHTMLTemplate($template)
762
    {
763
        if (substr($template ?? '', -3) == '.ss') {
764
            $template = substr($template ?? '', 0, -3);
765
        }
766
        $this->HTMLTemplate = $template;
767
768
        return $this;
769
    }
770
771
    /**
772
     * Get the template to render the plain part with
773
     *
774
     * @return string
775
     */
776
    public function getPlainTemplate()
777
    {
778
        return $this->plainTemplate;
779
    }
780
781
    /**
782
     * Set the template to render the plain part with
783
     *
784
     * @param string $template
785
     * @return $this
786
     */
787
    public function setPlainTemplate($template)
788
    {
789
        if (substr($template ?? '', -3) == '.ss') {
790
            $template = substr($template ?? '', 0, -3);
791
        }
792
        $this->plainTemplate = $template;
793
794
        return $this;
795
    }
796
797
    /**
798
     * @param array $recipients
799
     * @return $this
800
     */
801
    public function setFailedRecipients($recipients)
802
    {
803
        $this->failedRecipients = $recipients;
804
805
        return $this;
806
    }
807
808
    /**
809
     * @return array
810
     */
811
    public function getFailedRecipients()
812
    {
813
        return $this->failedRecipients;
814
    }
815
816
    /**
817
     * Used by {@link SSViewer} templates to detect if we're rendering an email template rather than a page template
818
     *
819
     * @return bool
820
     */
821
    public function IsEmail()
822
    {
823
        return true;
824
    }
825
826
    /**
827
     * Send the message to the recipients
828
     *
829
     * @return bool true if successful or array of failed recipients
830
     */
831
    public function send()
832
    {
833
        if (!$this->getBody()) {
834
            $this->render();
835
        }
836
        if (!$this->hasPlainPart()) {
837
            $this->generatePlainPartFromBody();
838
        }
839
        return Injector::inst()->get(Mailer::class)->send($this);
840
    }
841
842
    /**
843
     * @return array|bool
844
     */
845
    public function sendPlain()
846
    {
847
        if (!$this->hasPlainPart()) {
848
            $this->render(true);
849
        }
850
        return Injector::inst()->get(Mailer::class)->send($this);
851
    }
852
853
    /**
854
     * Render the email
855
     * @param bool $plainOnly Only render the message as plain text
856
     * @return $this
857
     */
858
    public function render($plainOnly = false)
859
    {
860
        if ($existingPlainPart = $this->findPlainPart()) {
861
            $this->getSwiftMessage()->detach($existingPlainPart);
862
        }
863
        unset($existingPlainPart);
864
865
        // Respect explicitly set body
866
        $htmlPart = $plainOnly ? null : $this->getBody();
867
        $plainPart = $plainOnly ? $this->getBody() : null;
868
869
        // Ensure we can at least render something
870
        $htmlTemplate = $this->getHTMLTemplate();
871
        $plainTemplate = $this->getPlainTemplate();
872
        if (!$htmlTemplate && !$plainTemplate && !$plainPart && !$htmlPart) {
873
            return $this;
874
        }
875
876
        // Do not interfere with emails styles
877
        Requirements::clear();
878
879
        // Render plain part
880
        if ($plainTemplate && !$plainPart) {
881
            $plainPart = $this->renderWith($plainTemplate, $this->getData())->Plain();
0 ignored issues
show
Bug introduced by
It seems like $this->getData() can also be of type SilverStripe\View\ViewableData; however, parameter $customFields of SilverStripe\View\ViewableData::renderWith() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

881
            $plainPart = $this->renderWith($plainTemplate, /** @scrutinizer ignore-type */ $this->getData())->Plain();
Loading history...
882
        }
883
884
        // Render HTML part, either if sending html email, or a plain part is lacking
885
        if (!$htmlPart && $htmlTemplate && (!$plainOnly || empty($plainPart))) {
886
            $htmlPart = $this->renderWith($htmlTemplate, $this->getData());
887
        }
888
889
        // Plain part fails over to generated from html
890
        if (!$plainPart && $htmlPart) {
891
            /** @var DBHTMLText $htmlPartObject */
892
            $htmlPartObject = DBField::create_field('HTMLFragment', $htmlPart);
893
            $plainPart = $htmlPartObject->Plain();
894
        }
895
896
        // Rendering is finished
897
        Requirements::restore();
898
899
        // Fail if no email to send
900
        if (!$plainPart && !$htmlPart) {
901
            return $this;
902
        }
903
904
        // Build HTML / Plain components
905
        if ($htmlPart && !$plainOnly) {
906
            $this->setBody($htmlPart);
907
            $this->getSwiftMessage()->setContentType('text/html');
908
            $this->getSwiftMessage()->setCharset('utf-8');
909
            if ($plainPart) {
910
                $this->getSwiftMessage()->addPart($plainPart, 'text/plain', 'utf-8');
911
            }
912
        } else {
913
            if ($plainPart) {
914
                $this->setBody($plainPart);
915
            }
916
            $this->getSwiftMessage()->setContentType('text/plain');
917
            $this->getSwiftMessage()->setCharset('utf-8');
918
        }
919
920
        return $this;
921
    }
922
923
    /**
924
     * @return Swift_MimePart|false
925
     */
926
    public function findPlainPart()
927
    {
928
        foreach ($this->getSwiftMessage()->getChildren() as $child) {
929
            if ($child instanceof Swift_MimePart && $child->getContentType() == 'text/plain') {
930
                return $child;
931
            }
932
        }
933
        return false;
934
    }
935
936
    /**
937
     * @return bool
938
     */
939
    public function hasPlainPart()
940
    {
941
        if ($this->getSwiftMessage()->getContentType() === 'text/plain') {
942
            return true;
943
        }
944
        return (bool) $this->findPlainPart();
945
    }
946
947
    /**
948
     * Automatically adds a plain part to the email generated from the current Body
949
     *
950
     * @return $this
951
     */
952
    public function generatePlainPartFromBody()
953
    {
954
        $plainPart = $this->findPlainPart();
955
        if ($plainPart) {
956
            $this->getSwiftMessage()->detach($plainPart);
957
        }
958
        unset($plainPart);
959
960
        $this->getSwiftMessage()->addPart(
961
            Convert::xml2raw($this->getBody()),
962
            'text/plain',
963
            'utf-8'
964
        );
965
966
        return $this;
967
    }
968
}
969