Passed
Pull Request — master (#19)
by Sergey
02:28
created

BetterEmail::makeTemplateCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 7
rs 10
1
<?php
2
3
namespace LeKoala\EmailTemplates\Email;
4
5
use Exception;
6
use BadMethodCallException;
7
use LeKoala\EmailTemplates\Extensions\EmailTemplateSiteConfigExtension;
8
use SilverStripe\i18n\i18n;
9
use SilverStripe\Control\HTTP;
10
use SilverStripe\View\SSViewer;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\Security\Member;
13
use SilverStripe\Control\Director;
14
use SilverStripe\View\Requirements;
15
use SilverStripe\Control\Email\Email;
16
use SilverStripe\SiteConfig\SiteConfig;
17
use LeKoala\EmailTemplates\Models\SentEmail;
18
use LeKoala\EmailTemplates\Helpers\EmailUtils;
19
use LeKoala\EmailTemplates\Models\EmailTemplate;
0 ignored issues
show
Bug introduced by
The type LeKoala\EmailTemplates\Models\EmailTemplate was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
20
use LeKoala\EmailTemplates\Helpers\SubsiteHelper;
21
use SilverStripe\Security\DefaultAdminService;
22
use SilverStripe\View\ViewableData;
23
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
24
use Symfony\Component\Mime\Part\AbstractPart;
25
26
/**
27
 * An improved and more pleasant base Email class to use on your project
28
 *
29
 * This class is fully decoupled from the EmailTemplate class and keep be used
30
 * independantly
31
 *
32
 * Improvements are:
33
 *
34
 * - URL safe rewriting
35
 * - Configurable base template (base system use Email class with setHTMLTemplate to provide content)
36
 * - Send email according to member locale
37
 * - Check for subject
38
 * - Send to member or admin
39
 * - Persist emails
40
 * - Parse body (multi part body is supported)
41
 * - Plaintext takes template into account
42
 * - Disable emails
43
 * - Unified send methods that support hooks
44
 *
45
 * @author lekoala
46
 */
47
class BetterEmail extends Email
48
{
49
    const STATE_CANCELLED = 'cancelled';
50
    const STATE_NOT_SENT = 'not_sent';
51
    const STATE_SENT = 'sent';
52
    const STATE_FAILED = 'failed';
53
54
    /**
55
     * @var EmailTemplate|null
56
     */
57
    protected $emailTemplate;
58
59
    /**
60
     * @var string
61
     */
62
    protected $locale;
63
64
    /**
65
     * @var string
66
     */
67
    protected $to;
68
69
    /**
70
     * @var Member|null
71
     */
72
    protected $to_member;
73
74
    /**
75
     * @var string
76
     */
77
    protected $from;
78
79
    /**
80
     * @var Member|null
81
     */
82
    protected $from_member;
83
84
    /**
85
     * @var boolean
86
     */
87
    protected $disabled = false;
88
89
    /**
90
     * @var SentEmail|null
91
     */
92
    protected $sentMail = null;
93
94
    /**
95
     * @var boolean
96
     */
97
    protected $sendingCancelled = false;
98
99
    /**
100
     * Email constructor.
101
     * @param string|array $from
102
     * @param string|array $to
103
     * @param string $subject
104
     * @param string $body
105
     * @param string|array $cc
106
     * @param string|array $bcc
107
     * @param string $returnPath
108
     */
109
    public function __construct(
110
        string|array $from = '',
111
        string|array $to = '',
112
        string $subject = '',
113
        string $body = '',
114
        string|array $cc = '',
115
        string|array $bcc = '',
116
        string $returnPath = ''
117
    ) {
118
        parent::__construct($from, $to, $subject, $body, $cc, $bcc, $returnPath);
119
120
        // Use template as a layout
121
        if ($defaultTemplate = self::config()->get('template')) {
122
            // Call method because variable is private
123
            parent::setHTMLTemplate($defaultTemplate);
124
        }
125
    }
126
127
    /**
128
     * Persists the email to the database
129
     *
130
     * @param bool|array|string $results
131
     * @return SentEmail
132
     */
133
    protected function persist($results)
134
    {
135
        $record = SentEmail::create([
136
            'To' => EmailUtils::format_email_addresses($this->getTo()),
137
            'From' => EmailUtils::format_email_addresses($this->getFrom()),
138
            'ReplyTo' => EmailUtils::format_email_addresses($this->getReplyTo()),
139
            'Subject' => $this->getSubject(),
140
            'Body' => $this->getRenderedBody(),
141
            'Headers' => $this->getHeaders()->toString(),
142
            'CC' => EmailUtils::format_email_addresses($this->getCC()),
143
            'BCC' => EmailUtils::format_email_addresses($this->getBCC()),
144
            'Results' => json_encode($results),
145
        ]);
146
        $record->write();
147
148
        // TODO: migrate this to a cron task
149
        SentEmail::cleanup();
150
151
        return $record;
152
    }
153
154
155
    /**
156
     * Get body of message after rendering
157
     * Useful for previews
158
     *
159
     * @return string
160
     */
161
    public function getRenderedBody()
162
    {
163
        $this->render();
164
        return $this->getHtmlBody();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getHtmlBody() also could return the type resource which is incompatible with the documented return type string.
Loading history...
165
    }
166
167
    /**
168
     * Don't forget that setBody will erase content of html template
169
     * Prefer to use this instead. Basically you can replace setBody calls with this method
170
     * URLs are rewritten by render process
171
     *
172
     * @param string $body
173
     * @return static
174
     */
175
    public function addBody($body)
176
    {
177
        return $this->addData("EmailContent", $body);
178
    }
179
180
    /**
181
     * @param string $body The email body
182
     * @return static
183
     */
184
    public function setBody(AbstractPart|string $body = null): static
185
    {
186
        $this->text(null);
187
188
        $body = self::rewriteURLs($body);
189
        parent::setBody($body);
190
191
        return $this;
192
    }
193
194
    /**
195
     * @param array|ViewableData $data The template data to set
196
     * @return $this
197
     */
198
    public function setData($data)
199
    {
200
        // Merge data!
201
        if ($this->emailTemplate) {
202
            if (is_array($data)) {
203
                parent::addData($data);
204
            } elseif ($data instanceof DataObject) {
205
                parent::addData($data->toMap());
206
            } else {
207
                parent::setData($data);
208
            }
209
        } else {
210
            parent::setData($data);
211
        }
212
        return $this;
213
    }
214
215
    /**
216
     * Sends a HTML email
217
     *
218
     * @return void
219
     */
220
    public function send(): void
221
    {
222
        $this->doSend(false);
223
    }
224
225
    /**
226
     * Sends a plain text email
227
     *
228
     * @return void
229
     */
230
    public function sendPlain(): void
231
    {
232
        $this->doSend(true);
233
    }
234
235
    /**
236
     * Send this email
237
     *
238
     * @param bool $plain
239
     * @return bool|string true if successful or error string on failure
240
     * @throws Exception
241
     */
242
    public function doSend($plain = false)
243
    {
244
        if ($this->disabled) {
245
            $this->sendingCancelled = true;
246
            return false;
247
        }
248
249
        // Check for Subject
250
        if (!$this->getSubject()) {
251
            throw new BadMethodCallException('You must set a subject');
252
        }
253
254
        // This hook can prevent email from being sent
255
        $result = $this->extend('onBeforeDoSend', $this);
256
        if ($result === false) {
0 ignored issues
show
introduced by
The condition $result === false is always false.
Loading history...
257
            $this->sendingCancelled = true;
258
            return false;
259
        }
260
261
        $SiteConfig = $this->currentSiteConfig();
262
263
        // Check for Sender and use default if necessary
264
        $from = $this->getFrom();
265
        if (empty($from)) {
266
            $this->setFrom($SiteConfig->EmailDefaultSender());
267
        }
268
269
        // Check for Recipient and use default if necessary
270
        $to = $this->getTo();
271
        if (empty($to)) {
272
            $this->addTo($SiteConfig->EmailDefaultRecipient());
273
        }
274
275
        // Set language to use for the email
276
        $restore_locale = null;
277
        if ($this->locale) {
278
            $restore_locale = i18n::get_locale();
279
            i18n::set_locale($this->locale);
280
        }
281
282
        $member = $this->to_member;
283
        if ($member) {
284
            // Maybe this member doesn't want to receive emails?
285
            // @phpstan-ignore-next-line
286
            if ($member->hasMethod('canReceiveEmails') && !$member->canReceiveEmails()) {
287
                return false;
288
            }
289
        }
290
291
        // Make sure we have a full render with current locale
292
        if ($this->emailTemplate) {
293
            $this->clearBody();
294
        }
295
296
        try {
297
            $res = true;
298
            if ($plain) {
299
                parent::sendPlain();
300
            } else {
301
                parent::send();
302
            }
303
        } catch (TransportExceptionInterface $th) {
304
            $res = $th->getMessage();
305
        }
306
307
        if ($restore_locale) {
308
            i18n::set_locale($restore_locale);
309
        }
310
311
        $this->extend('onAfterDoSend', $this, $res);
312
        $this->sentMail = $this->persist($res);
313
314
        return $res;
315
    }
316
317
    /**
318
     * Returns one of the STATE_xxxx constant
319
     *
320
     * @return string
321
     */
322
    public function getSendStatus()
323
    {
324
        if ($this->sendingCancelled) {
325
            return self::STATE_CANCELLED;
326
        }
327
        if ($this->sentMail) {
328
            if ($this->sentMail->IsSuccess()) {
329
                return self::STATE_SENT;
330
            }
331
            return self::STATE_FAILED;
332
        }
333
        return self::STATE_NOT_SENT;
334
    }
335
336
    /**
337
     * Was sending cancelled ?
338
     *
339
     * @return bool
340
     */
341
    public function getSendingCancelled()
342
    {
343
        return $this->sendingCancelled;
344
    }
345
346
    /**
347
     * The last result from "send" method. Null if not sent yet or sending was cancelled
348
     *
349
     * @return SentEmail
350
     */
351
    public function getSentMail()
352
    {
353
        return $this->sentMail;
354
    }
355
356
    /**
357
     * @return $this
358
     */
359
    public function clearBody()
360
    {
361
        $this->setBody(null);
362
        return $this;
363
    }
364
365
    /**
366
     * Set the template to render the email with
367
     *
368
     * This method is overidden in order to look for email templates to provide
369
     * content to
370
     *
371
     * @param string $template
372
     * @return static
373
     */
374
    public function setHTMLTemplate(string $template): static
375
    {
376
        if (substr($template, -3) == '.ss') {
377
            $template = substr($template, 0, -3);
378
        }
379
380
        // Do we have a custom template matching this code?
381
        $code = self::makeTemplateCode($template);
382
        $emailTemplate = EmailTemplate::getByCode($code, false);
383
        if ($emailTemplate) {
384
            $emailTemplate->applyTemplate($this);
385
            return $this;
386
        }
387
388
        // If not, keep default behaviour (call method because var is private)
389
        return parent::setHTMLTemplate($template);
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::setHTMLTemplate($template) returns the type SilverStripe\Control\Email\Email which includes types incompatible with the type-hinted return LeKoala\EmailTemplates\Email\BetterEmail.
Loading history...
390
    }
391
392
    /**
393
     * Make a template code
394
     *
395
     * @param string $str
396
     * @return string
397
     */
398
    public static function makeTemplateCode($str)
399
    {
400
        // If we get a class name
401
        $parts = explode('\\', $str);
402
        $str = end($parts);
403
        $code = preg_replace('/Email$/', '', $str);
404
        return $code;
405
    }
406
407
    /**
408
     * Helper method to render string with data
409
     *
410
     * @param string $content
411
     * @return string
412
     */
413
    public function renderWithData($content)
414
    {
415
        $viewer = SSViewer::fromString($content);
416
        $data = $this->getData();
417
        
418
        $result = (string) $viewer->process($data);
419
        $result = self::rewriteURLs($result);
420
        return $result;
421
    }
422
423
    /**
424
     * Render the email
425
     * @param bool $plainOnly Only render the message as plain text
426
     * @return $this
427
     */
428
    public function render($plainOnly = false)
429
    {
430
        $this->text(null);
431
432
        // Respect explicitly set body
433
        $htmlPart = $plainPart = null;
434
435
        // Only respect if we don't have an email template
436
        if ($this->emailTemplate) {
437
            $htmlPart = $plainOnly ? null : $this->getHtmlBody();
438
            $plainPart = $plainOnly ? $this->getTextBody() : null;
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getTextBody() targeting Symfony\Component\Mime\Email::getTextBody() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
439
        }
440
441
        // Ensure we can at least render something
442
        $htmlTemplate = $this->getHTMLTemplate();
443
        $plainTemplate = $this->getPlainTemplate();
444
        if (!$htmlTemplate && !$plainTemplate && !$plainPart && !$htmlPart) {
445
            return $this;
446
        }
447
448
        // Do not interfere with emails styles
449
        Requirements::clear();
450
451
        // Render plain part
452
        if ($plainTemplate && !$plainPart) {
453
            $plainPart = $this->getData()->renderWith($plainTemplate, $this->getData())->Plain();
0 ignored issues
show
Bug introduced by
$this->getData() of type SilverStripe\View\ViewableData is incompatible with the type array expected by parameter $customFields of SilverStripe\View\ViewableData::renderWith(). ( Ignorable by Annotation )

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

453
            $plainPart = $this->getData()->renderWith($plainTemplate, /** @scrutinizer ignore-type */ $this->getData())->Plain();
Loading history...
454
            // Do another round of rendering to render our variables inside
455
            $plainPart = $this->renderWithData($plainPart);
456
        }
457
458
        // Render HTML part, either if sending html email, or a plain part is lacking
459
        if (!$htmlPart && $htmlTemplate && (!$plainOnly || empty($plainPart))) {
460
            $htmlPart = $this->getData()->renderWith($htmlTemplate, $this->getData());
461
            // Do another round of rendering to render our variables inside
462
            $htmlPart = $this->renderWithData($htmlPart);
463
        }
464
465
        // Render subject with data as well
466
        $subject = $this->renderWithData($this->getSubject());
467
        // Html entities in email titles is not a good idea
468
        $subject = html_entity_decode($subject, ENT_QUOTES | ENT_XML1, 'UTF-8');
469
        // Avoid crazy template name in email
470
        $subject = preg_replace("/<!--(.)+-->/", "", $subject);
471
        parent::setSubject($subject);
472
473
        // Plain part fails over to generated from html
474
        if (!$plainPart && $htmlPart) {
475
            $plainPart = EmailUtils::convert_html_to_text($htmlPart);
0 ignored issues
show
Bug introduced by
It seems like $htmlPart can also be of type resource; however, parameter $content of LeKoala\EmailTemplates\H...:convert_html_to_text() does only seem to accept Symfony\Component\Mime\Part\TextPart|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

475
            $plainPart = EmailUtils::convert_html_to_text(/** @scrutinizer ignore-type */ $htmlPart);
Loading history...
476
        }
477
478
        // Rendering is finished
479
        Requirements::restore();
480
481
        // Fail if no email to send
482
        if (!$plainPart && !$htmlPart) {
483
            return $this;
484
        }
485
486
        // Build HTML / Plain components
487
        if ($htmlPart && !$plainOnly) {
488
            $this->setBody($htmlPart);
0 ignored issues
show
Bug introduced by
It seems like $htmlPart can also be of type resource; however, parameter $body of LeKoala\EmailTemplates\E...\BetterEmail::setBody() does only seem to accept Symfony\Component\Mime\P...bstractPart|null|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

488
            $this->setBody(/** @scrutinizer ignore-type */ $htmlPart);
Loading history...
489
        }
490
        $this->text($plainPart, 'utf-8');
491
492
        return $this;
493
    }
494
495
    /**
496
     * Get locale set before email is sent
497
     *
498
     * @return string
499
     */
500
    public function getLocale()
501
    {
502
        return $this->locale;
503
    }
504
505
    /**
506
     *  Set locale to set before email is sent
507
     *
508
     * @param string $val
509
     */
510
    public function setLocale($val)
511
    {
512
        $this->locale = $val;
513
    }
514
515
    /**
516
     * Is this email disabled ?
517
     *
518
     * @return boolean
519
     */
520
    public function getDisabled()
521
    {
522
        return $this->disabled;
523
    }
524
525
    /**
526
     * Disable this email (sending will have no effect)
527
     *
528
     * @param bool $disabled
529
     * @return $this
530
     */
531
    public function setDisabled($disabled)
532
    {
533
        $this->disabled = (bool) $disabled;
534
        return $this;
535
    }
536
537
    /**
538
     * Get recipient as member
539
     *
540
     * @return Member
541
     */
542
    public function getToMember()
543
    {
544
        if (!$this->to_member && $this->to) {
545
            $email = EmailUtils::get_email_from_rfc_email($this->to);
546
            $member = Member::get()->filter(['Email' => $email])->first();
547
            if ($member) {
548
                $this->setToMember($member);
549
            }
550
        }
551
        return $this->to_member;
552
    }
553
554
    /**
555
     * Set recipient(s) of the email
556
     *
557
     * To send to many, pass an array:
558
     * array('[email protected]' => 'My Name', '[email protected]');
559
     *
560
     * @param string|array $address The message recipient(s) - if sending to multiple, use an array of address => name
561
     * @param string $name The name of the recipient (if one)
562
     * @return static
563
     */
564
    public function setTo(string|array $address, string $name = ''): static
565
    {
566
        // Allow Name <[email protected]>
567
        if (!$name && is_string($address)) {
0 ignored issues
show
introduced by
The condition is_string($address) is always false.
Loading history...
568
            $name = EmailUtils::get_displayname_from_rfc_email($address);
569
            $address = EmailUtils::get_email_from_rfc_email($address);
570
        }
571
        // Make sure this doesn't conflict with to_member property
572
        if ($this->to_member) {
573
            if (is_string($address)) {
0 ignored issues
show
introduced by
The condition is_string($address) is always false.
Loading history...
574
                // We passed an email that doesn't match to member
575
                if ($this->to_member->Email != $address) {
576
                    $this->to_member = null;
577
                }
578
            } else {
579
                $this->to_member = null;
580
            }
581
        }
582
        $this->to = $address;
0 ignored issues
show
Documentation Bug introduced by
It seems like $address of type array is incompatible with the declared type string of property $to.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
583
        return parent::setTo($address, $name);
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::setTo($address, $name) returns the type SilverStripe\Control\Email\Email which includes types incompatible with the type-hinted return LeKoala\EmailTemplates\Email\BetterEmail.
Loading history...
584
    }
585
586
587
588
    /**
589
     * @param string $subject The Subject line for the email
590
     * @return static
591
     */
592
    public function setSubject(string $subject): static
593
    {
594
        // Do not allow changing subject if a template is set
595
        if ($this->emailTemplate && $this->getSubject()) {
596
            return $this;
597
        }
598
        return parent::setSubject($subject);
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::setSubject($subject) returns the type SilverStripe\Control\Email\Email which includes types incompatible with the type-hinted return LeKoala\EmailTemplates\Email\BetterEmail.
Loading history...
599
    }
600
601
    /**
602
     * Send to admin
603
     *
604
     * @return Email
605
     */
606
    public function setToAdmin()
607
    {
608
        $admin = DefaultAdminService::singleton()->findOrCreateDefaultAdmin();
609
        return $this->setToMember($admin);
610
    }
611
612
    /**
613
     * Wrapper to report proper SiteConfig type
614
     *
615
     * @return SiteConfig|EmailTemplateSiteConfigExtension
616
     */
617
    public function currentSiteConfig()
618
    {
619
        /** @var SiteConfig|EmailTemplateSiteConfigExtension */
620
        return SiteConfig::current_site_config();
621
    }
622
    /**
623
     * Set to
624
     *
625
     * @return Email
626
     */
627
    public function setToContact()
628
    {
629
        $email = $this->currentSiteConfig()->EmailDefaultRecipient();
630
        return $this->setTo($email);
631
    }
632
633
    /**
634
     * Add in bcc admin
635
     *
636
     * @return Email
637
     */
638
    public function bccToAdmin()
639
    {
640
        $admin = DefaultAdminService::singleton()->findOrCreateDefaultAdmin();
641
        return $this->addBCC($admin->Email);
642
    }
643
644
    /**
645
     * Add in bcc admin
646
     *
647
     * @return Email
648
     */
649
    public function bccToContact()
650
    {
651
        $email = $this->currentSiteConfig()->EmailDefaultRecipient();
652
        return $this->addBCC($email);
653
    }
654
655
    /**
656
     * Set a member as a recipient.
657
     *
658
     * It will also set the $Recipient variable in the template
659
     *
660
     * @param Member $member
661
     * @param string $locale Locale to use, set to false to keep current locale
662
     * @return BetterEmail
663
     */
664
    public function setToMember(Member $member, $locale = null)
665
    {
666
        if ($locale === null) {
667
            $this->locale = $member->Locale;
668
        } else {
669
            $this->locale = $locale;
670
        }
671
        $this->to_member = $member;
672
673
        $this->addData(['Recipient' => $member]);
674
675
        return $this->setTo($member->Email, $member->getTitle());
676
    }
677
678
    /**
679
     * Get sender as member
680
     *
681
     * @return Member
682
     */
683
    public function getFromMember()
684
    {
685
        if (!$this->from_member && $this->from) {
686
            $email = EmailUtils::get_email_from_rfc_email($this->from);
687
            $member = Member::get()->filter(['Email' => $email])->first();
688
            if ($member) {
689
                $this->setFromMember($member);
690
            }
691
        }
692
        return $this->from_member;
693
    }
694
695
    /**
696
     * Set From Member
697
     *
698
     * It will also set the $Sender variable in the template
699
     *
700
     * @param Member $member
701
     * @return BetterEmail
702
     */
703
    public function setFromMember(Member $member)
704
    {
705
        $this->from_member = $member;
706
707
        $this->addData(['Sender' => $member]);
708
709
        return $this->setFrom($member->Email, $member->getTitle());
710
    }
711
712
    /**
713
     * Improved set from that supports Name <[email protected]> notation
714
     *
715
     * @param string|array $address
716
     * @param string $name
717
     * @return static
718
     */
719
    public function setFrom(string|array $address, string $name = ''): static
720
    {
721
        if (!$name && is_string($address)) {
0 ignored issues
show
introduced by
The condition is_string($address) is always false.
Loading history...
722
            $name = EmailUtils::get_displayname_from_rfc_email($address);
723
            $address = EmailUtils::get_email_from_rfc_email($address);
724
        }
725
        $this->from = $address;
0 ignored issues
show
Documentation Bug introduced by
It seems like $address of type array is incompatible with the declared type string of property $from.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
726
        return parent::setFrom($address, $name);
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::setFrom($address, $name) returns the type SilverStripe\Control\Email\Email which includes types incompatible with the type-hinted return LeKoala\EmailTemplates\Email\BetterEmail.
Loading history...
727
    }
728
729
    /**
730
     * Bug safe absolute url that support subsites
731
     *
732
     * @param string $url
733
     * @param bool $relativeToSiteBase
734
     * @return string
735
     */
736
    protected static function safeAbsoluteURL($url, $relativeToSiteBase = false)
737
    {
738
        if (empty($url)) {
739
            $absUrl = Director::baseURL();
740
        } else {
741
            $firstCharacter = substr($url, 0, 1);
742
743
            // It's a merge tag, don't touch it because we don't know what kind of url it contains
744
            if (in_array($firstCharacter, ['*', '$', '%'])) {
745
                return $url;
746
            }
747
748
            $absUrl = Director::absoluteURL($url, $relativeToSiteBase ? Director::BASE : Director::ROOT);
749
        }
750
751
        // If we use subsite, absolute url may not use the proper url
752
        $absUrl = SubsiteHelper::safeAbsoluteURL($absUrl);
753
754
        return $absUrl;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $absUrl could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
755
    }
756
757
    /**
758
     * Turn all relative URLs in the content to absolute URLs
759
     */
760
    protected static function rewriteURLs($html)
761
    {
762
        if (isset($_SERVER['REQUEST_URI'])) {
763
            $html = str_replace('$CurrentPageURL', $_SERVER['REQUEST_URI'], $html ?? '');
764
        }
765
        return HTTP::urlRewriter($html, function ($url) {
766
            //no need to rewrite, if uri has a protocol (determined here by existence of reserved URI character ":")
767
            if (preg_match('/^\w+:/', $url)) {
768
                return $url;
769
            }
770
            return self::safeAbsoluteURL($url, true);
771
        });
772
    }
773
774
    /**
775
     * Get the value of emailTemplate
776
     * @return EmailTemplate
777
     */
778
    public function getEmailTemplate()
779
    {
780
        return $this->emailTemplate;
781
    }
782
783
    /**
784
     * Set the value of emailTemplate
785
     *
786
     * @param EmailTemplate $emailTemplate
787
     * @return $this
788
     */
789
    public function setEmailTemplate(EmailTemplate $emailTemplate)
790
    {
791
        $this->emailTemplate = $emailTemplate;
792
        return $this;
793
    }
794
}
795