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

BetterEmail::rewriteURLs()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 11
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
                // sendPlain will trigger our updated generatePlainPartFromBody
300
                parent::sendPlain();
301
            } else {
302
                parent::send();
303
            }
304
        } catch (TransportExceptionInterface $th) {
305
            $res = $th->getMessage();
306
        }
307
308
        if ($restore_locale) {
309
            i18n::set_locale($restore_locale);
310
        }
311
312
        $this->extend('onAfterDoSend', $this, $res);
313
        $this->sentMail = $this->persist($res);
314
315
        return $res;
316
    }
317
318
    /**
319
     * Returns one of the STATE_xxxx constant
320
     *
321
     * @return string
322
     */
323
    public function getSendStatus()
324
    {
325
        if ($this->sendingCancelled) {
326
            return self::STATE_CANCELLED;
327
        }
328
        if ($this->sentMail) {
329
            if ($this->sentMail->IsSuccess()) {
330
                return self::STATE_SENT;
331
            }
332
            return self::STATE_FAILED;
333
        }
334
        return self::STATE_NOT_SENT;
335
    }
336
337
    /**
338
     * Was sending cancelled ?
339
     *
340
     * @return bool
341
     */
342
    public function getSendingCancelled()
343
    {
344
        return $this->sendingCancelled;
345
    }
346
347
    /**
348
     * The last result from "send" method. Null if not sent yet or sending was cancelled
349
     *
350
     * @return SentEmail
351
     */
352
    public function getSentMail()
353
    {
354
        return $this->sentMail;
355
    }
356
357
    /**
358
     * Automatically adds a plain part to the email generated from the current Body
359
     *
360
     * @return $this
361
     */
362
    public function generatePlainPartFromBody()
363
    {
364
        $this->text(
365
            EmailUtils::convert_html_to_text($this->getBody()->bodyToString()),
366
            'utf-8'
367
        );
368
369
        return $this;
370
    }
371
372
    /**
373
     * @return $this
374
     */
375
    public function clearBody()
376
    {
377
        $this->setBody(null);
378
        return $this;
379
    }
380
381
    /**
382
     * Set the template to render the email with
383
     *
384
     * This method is overidden in order to look for email templates to provide
385
     * content to
386
     *
387
     * @param string $template
388
     * @return static
389
     */
390
    public function setHTMLTemplate(string $template): static
391
    {
392
        if (substr($template, -3) == '.ss') {
393
            $template = substr($template, 0, -3);
394
        }
395
396
        // Do we have a custom template matching this code?
397
        $code = self::makeTemplateCode($template);
398
        $emailTemplate = EmailTemplate::getByCode($code, false);
399
        if ($emailTemplate) {
400
            $emailTemplate->applyTemplate($this);
401
            return $this;
402
        }
403
404
        // If not, keep default behaviour (call method because var is private)
405
        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...
406
    }
407
408
    /**
409
     * Make a template code
410
     *
411
     * @param string $str
412
     * @return string
413
     */
414
    public static function makeTemplateCode($str)
415
    {
416
        // If we get a class name
417
        $parts = explode('\\', $str);
418
        $str = end($parts);
419
        $code = preg_replace('/Email$/', '', $str);
420
        return $code;
421
    }
422
423
    /**
424
     * Helper method to render string with data
425
     *
426
     * @param string $content
427
     * @return string
428
     */
429
    public function renderWithData($content)
430
    {
431
        $viewer = SSViewer::fromString($content);
432
        $data = $this->getData();
433
        
434
        $result = (string) $viewer->process($data);
435
        $result = self::rewriteURLs($result);
436
        return $result;
437
    }
438
439
    /**
440
     * Render the email
441
     * @param bool $plainOnly Only render the message as plain text
442
     * @return $this
443
     */
444
    public function render($plainOnly = false)
445
    {
446
        $this->text(null);
447
448
        // Respect explicitly set body
449
        $htmlPart = $plainPart = null;
450
451
        // Only respect if we don't have an email template
452
        if ($this->emailTemplate) {
453
            $htmlPart = $plainOnly ? null : $this->getBody();
454
            $plainPart = $plainOnly ? $this->getBody() : null;
455
        }
456
457
        // Ensure we can at least render something
458
        $htmlTemplate = $this->getHTMLTemplate();
459
        $plainTemplate = $this->getPlainTemplate();
460
        if (!$htmlTemplate && !$plainTemplate && !$plainPart && !$htmlPart) {
461
            return $this;
462
        }
463
464
        // Do not interfere with emails styles
465
        Requirements::clear();
466
467
        // Render plain part
468
        if ($plainTemplate && !$plainPart) {
469
            $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

469
            $plainPart = $this->getData()->renderWith($plainTemplate, /** @scrutinizer ignore-type */ $this->getData())->Plain();
Loading history...
470
            // Do another round of rendering to render our variables inside
471
            $plainPart = $this->renderWithData($plainPart);
472
        }
473
474
        // Render HTML part, either if sending html email, or a plain part is lacking
475
        if (!$htmlPart && $htmlTemplate && (!$plainOnly || empty($plainPart))) {
476
            $htmlPart = $this->getData()->renderWith($htmlTemplate, $this->getData());
477
            // Do another round of rendering to render our variables inside
478
            $htmlPart = $this->renderWithData($htmlPart);
479
        }
480
481
        // Render subject with data as well
482
        $subject = $this->renderWithData($this->getSubject());
483
        // Html entities in email titles is not a good idea
484
        $subject = html_entity_decode($subject, ENT_QUOTES | ENT_XML1, 'UTF-8');
485
        // Avoid crazy template name in email
486
        $subject = preg_replace("/<!--(.)+-->/", "", $subject);
487
        parent::setSubject($subject);
488
489
        // Plain part fails over to generated from html
490
        if (!$plainPart && $htmlPart) {
491
            $plainPart = EmailUtils::convert_html_to_text($htmlPart);
0 ignored issues
show
Bug introduced by
It seems like $htmlPart can also be of type Symfony\Component\Mime\Part\AbstractPart; however, parameter $content of LeKoala\EmailTemplates\H...:convert_html_to_text() 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

491
            $plainPart = EmailUtils::convert_html_to_text(/** @scrutinizer ignore-type */ $htmlPart);
Loading history...
492
        }
493
494
        // Rendering is finished
495
        Requirements::restore();
496
497
        // Fail if no email to send
498
        if (!$plainPart && !$htmlPart) {
499
            return $this;
500
        }
501
502
        // Build HTML / Plain components
503
        if ($htmlPart && !$plainOnly) {
504
            $this->setBody($htmlPart);
505
        }
506
        $this->text($plainPart, 'utf-8');
0 ignored issues
show
Bug introduced by
It seems like $plainPart can also be of type Symfony\Component\Mime\Part\AbstractPart; however, parameter $body of Symfony\Component\Mime\Email::text() does only seem to accept null|resource|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

506
        $this->text(/** @scrutinizer ignore-type */ $plainPart, 'utf-8');
Loading history...
507
508
        return $this;
509
    }
510
511
    /**
512
     * Get locale set before email is sent
513
     *
514
     * @return string
515
     */
516
    public function getLocale()
517
    {
518
        return $this->locale;
519
    }
520
521
    /**
522
     *  Set locale to set before email is sent
523
     *
524
     * @param string $val
525
     */
526
    public function setLocale($val)
527
    {
528
        $this->locale = $val;
529
    }
530
531
    /**
532
     * Is this email disabled ?
533
     *
534
     * @return boolean
535
     */
536
    public function getDisabled()
537
    {
538
        return $this->disabled;
539
    }
540
541
    /**
542
     * Disable this email (sending will have no effect)
543
     *
544
     * @param bool $disabled
545
     * @return $this
546
     */
547
    public function setDisabled($disabled)
548
    {
549
        $this->disabled = (bool) $disabled;
550
        return $this;
551
    }
552
553
    /**
554
     * Get recipient as member
555
     *
556
     * @return Member
557
     */
558
    public function getToMember()
559
    {
560
        if (!$this->to_member && $this->to) {
561
            $email = EmailUtils::get_email_from_rfc_email($this->to);
562
            $member = Member::get()->filter(['Email' => $email])->first();
563
            if ($member) {
564
                $this->setToMember($member);
565
            }
566
        }
567
        return $this->to_member;
568
    }
569
570
    /**
571
     * Set recipient(s) of the email
572
     *
573
     * To send to many, pass an array:
574
     * array('[email protected]' => 'My Name', '[email protected]');
575
     *
576
     * @param string|array $address The message recipient(s) - if sending to multiple, use an array of address => name
577
     * @param string $name The name of the recipient (if one)
578
     * @return static
579
     */
580
    public function setTo(string|array $address, string $name = ''): static
581
    {
582
        // Allow Name <[email protected]>
583
        if (!$name && is_string($address)) {
0 ignored issues
show
introduced by
The condition is_string($address) is always false.
Loading history...
584
            $name = EmailUtils::get_displayname_from_rfc_email($address);
585
            $address = EmailUtils::get_email_from_rfc_email($address);
586
        }
587
        // Make sure this doesn't conflict with to_member property
588
        if ($this->to_member) {
589
            if (is_string($address)) {
0 ignored issues
show
introduced by
The condition is_string($address) is always false.
Loading history...
590
                // We passed an email that doesn't match to member
591
                if ($this->to_member->Email != $address) {
592
                    $this->to_member = null;
593
                }
594
            } else {
595
                $this->to_member = null;
596
            }
597
        }
598
        $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...
599
        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...
600
    }
601
602
603
604
    /**
605
     * @param string $subject The Subject line for the email
606
     * @return static
607
     */
608
    public function setSubject(string $subject): static
609
    {
610
        // Do not allow changing subject if a template is set
611
        if ($this->emailTemplate && $this->getSubject()) {
612
            return $this;
613
        }
614
        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...
615
    }
616
617
    /**
618
     * Send to admin
619
     *
620
     * @return Email
621
     */
622
    public function setToAdmin()
623
    {
624
        $admin = DefaultAdminService::singleton()->findOrCreateDefaultAdmin();
625
        return $this->setToMember($admin);
626
    }
627
628
    /**
629
     * Wrapper to report proper SiteConfig type
630
     *
631
     * @return SiteConfig|EmailTemplateSiteConfigExtension
632
     */
633
    public function currentSiteConfig()
634
    {
635
        /** @var SiteConfig|EmailTemplateSiteConfigExtension */
636
        return SiteConfig::current_site_config();
637
    }
638
    /**
639
     * Set to
640
     *
641
     * @return Email
642
     */
643
    public function setToContact()
644
    {
645
        $email = $this->currentSiteConfig()->EmailDefaultRecipient();
646
        return $this->setTo($email);
647
    }
648
649
    /**
650
     * Add in bcc admin
651
     *
652
     * @return Email
653
     */
654
    public function bccToAdmin()
655
    {
656
        $admin = DefaultAdminService::singleton()->findOrCreateDefaultAdmin();
657
        return $this->addBCC($admin->Email);
658
    }
659
660
    /**
661
     * Add in bcc admin
662
     *
663
     * @return Email
664
     */
665
    public function bccToContact()
666
    {
667
        $email = $this->currentSiteConfig()->EmailDefaultRecipient();
668
        return $this->addBCC($email);
669
    }
670
671
    /**
672
     * Set a member as a recipient.
673
     *
674
     * It will also set the $Recipient variable in the template
675
     *
676
     * @param Member $member
677
     * @param string $locale Locale to use, set to false to keep current locale
678
     * @return BetterEmail
679
     */
680
    public function setToMember(Member $member, $locale = null)
681
    {
682
        if ($locale === null) {
683
            $this->locale = $member->Locale;
684
        } else {
685
            $this->locale = $locale;
686
        }
687
        $this->to_member = $member;
688
689
        $this->addData(['Recipient' => $member]);
690
691
        return $this->setTo($member->Email, $member->getTitle());
692
    }
693
694
    /**
695
     * Get sender as member
696
     *
697
     * @return Member
698
     */
699
    public function getFromMember()
700
    {
701
        if (!$this->from_member && $this->from) {
702
            $email = EmailUtils::get_email_from_rfc_email($this->from);
703
            $member = Member::get()->filter(['Email' => $email])->first();
704
            if ($member) {
705
                $this->setFromMember($member);
706
            }
707
        }
708
        return $this->from_member;
709
    }
710
711
    /**
712
     * Set From Member
713
     *
714
     * It will also set the $Sender variable in the template
715
     *
716
     * @param Member $member
717
     * @return BetterEmail
718
     */
719
    public function setFromMember(Member $member)
720
    {
721
        $this->from_member = $member;
722
723
        $this->addData(['Sender' => $member]);
724
725
        return $this->setFrom($member->Email, $member->getTitle());
726
    }
727
728
    /**
729
     * Improved set from that supports Name <[email protected]> notation
730
     *
731
     * @param string|array $address
732
     * @param string $name
733
     * @return static
734
     */
735
    public function setFrom(string|array $address, string $name = ''): static
736
    {
737
        if (!$name && is_string($address)) {
0 ignored issues
show
introduced by
The condition is_string($address) is always false.
Loading history...
738
            $name = EmailUtils::get_displayname_from_rfc_email($address);
739
            $address = EmailUtils::get_email_from_rfc_email($address);
740
        }
741
        $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...
742
        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...
743
    }
744
745
    /**
746
     * Bug safe absolute url that support subsites
747
     *
748
     * @param string $url
749
     * @param bool $relativeToSiteBase
750
     * @return string
751
     */
752
    protected static function safeAbsoluteURL($url, $relativeToSiteBase = false)
753
    {
754
        if (empty($url)) {
755
            $absUrl = Director::baseURL();
756
        } else {
757
            $firstCharacter = substr($url, 0, 1);
758
759
            // It's a merge tag, don't touch it because we don't know what kind of url it contains
760
            if (in_array($firstCharacter, ['*', '$', '%'])) {
761
                return $url;
762
            }
763
764
            $absUrl = Director::absoluteURL($url, $relativeToSiteBase ? Director::BASE : Director::ROOT);
765
        }
766
767
        // If we use subsite, absolute url may not use the proper url
768
        $absUrl = SubsiteHelper::safeAbsoluteURL($absUrl);
769
770
        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...
771
    }
772
773
    /**
774
     * Turn all relative URLs in the content to absolute URLs
775
     */
776
    protected static function rewriteURLs($html)
777
    {
778
        if (isset($_SERVER['REQUEST_URI'])) {
779
            $html = str_replace('$CurrentPageURL', $_SERVER['REQUEST_URI'], $html ?? '');
780
        }
781
        return HTTP::urlRewriter($html, function ($url) {
782
            //no need to rewrite, if uri has a protocol (determined here by existence of reserved URI character ":")
783
            if (preg_match('/^\w+:/', $url)) {
784
                return $url;
785
            }
786
            return self::safeAbsoluteURL($url, true);
787
        });
788
    }
789
790
    /**
791
     * Get the value of emailTemplate
792
     * @return EmailTemplate
793
     */
794
    public function getEmailTemplate()
795
    {
796
        return $this->emailTemplate;
797
    }
798
799
    /**
800
     * Set the value of emailTemplate
801
     *
802
     * @param EmailTemplate $emailTemplate
803
     * @return $this
804
     */
805
    public function setEmailTemplate(EmailTemplate $emailTemplate)
806
    {
807
        $this->emailTemplate = $emailTemplate;
808
        return $this;
809
    }
810
}
811