Email::_wrap()   F
last analyzed

Complexity

Conditions 22
Paths 33

Size

Total Lines 101

Duplication

Lines 10
Ratio 9.9 %

Importance

Changes 0
Metric Value
cc 22
nc 33
nop 2
dl 10
loc 101
rs 3.3333
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5
 *
6
 * Licensed under The MIT License
7
 * For full copyright and license information, please see the LICENSE.txt
8
 * Redistributions of files must retain the above copyright notice.
9
 *
10
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11
 * @link          https://cakephp.org CakePHP(tm) Project
12
 * @since         2.0.0
13
 * @license       https://opensource.org/licenses/mit-license.php MIT License
14
 */
15
namespace Cake\Mailer;
16
17
use BadMethodCallException;
18
use Cake\Core\Configure;
19
use Cake\Core\StaticConfigTrait;
20
use Cake\Filesystem\File;
21
use Cake\Http\Client\FormDataPart;
22
use Cake\Log\Log;
23
use Cake\Utility\Hash;
24
use Cake\Utility\Security;
25
use Cake\Utility\Text;
26
use Cake\View\ViewVarsTrait;
27
use Closure;
28
use Exception;
29
use InvalidArgumentException;
30
use JsonSerializable;
31
use LogicException;
32
use PDO;
33
use RuntimeException;
34
use Serializable;
35
use SimpleXMLElement;
36
37
/**
38
 * CakePHP Email class.
39
 *
40
 * This class is used for sending Internet Message Format based
41
 * on the standard outlined in https://www.rfc-editor.org/rfc/rfc2822.txt
42
 *
43
 * ### Configuration
44
 *
45
 * Configuration for Email is managed by Email::config() and Email::configTransport().
46
 * Email::config() can be used to add or read a configuration profile for Email instances.
47
 * Once made configuration profiles can be used to re-use across various email messages your
48
 * application sends.
49
 */
50
class Email implements JsonSerializable, Serializable
51
{
52
    use StaticConfigTrait;
53
    use ViewVarsTrait;
54
55
    /**
56
     * Line length - no should more - RFC 2822 - 2.1.1
57
     *
58
     * @var int
59
     */
60
    const LINE_LENGTH_SHOULD = 78;
61
62
    /**
63
     * Line length - no must more - RFC 2822 - 2.1.1
64
     *
65
     * @var int
66
     */
67
    const LINE_LENGTH_MUST = 998;
68
69
    /**
70
     * Type of message - HTML
71
     *
72
     * @var string
73
     */
74
    const MESSAGE_HTML = 'html';
75
76
    /**
77
     * Type of message - TEXT
78
     *
79
     * @var string
80
     */
81
    const MESSAGE_TEXT = 'text';
82
83
    /**
84
     * Holds the regex pattern for email validation
85
     *
86
     * @var string
87
     */
88
    const EMAIL_PATTERN = '/^((?:[\p{L}0-9.!#$%&\'*+\/=?^_`{|}~-]+)*@[\p{L}0-9-._]+)$/ui';
89
90
    /**
91
     * Recipient of the email
92
     *
93
     * @var array
94
     */
95
    protected $_to = [];
96
97
    /**
98
     * The mail which the email is sent from
99
     *
100
     * @var array
101
     */
102
    protected $_from = [];
103
104
    /**
105
     * The sender email
106
     *
107
     * @var array
108
     */
109
    protected $_sender = [];
110
111
    /**
112
     * The email the recipient will reply to
113
     *
114
     * @var array
115
     */
116
    protected $_replyTo = [];
117
118
    /**
119
     * The read receipt email
120
     *
121
     * @var array
122
     */
123
    protected $_readReceipt = [];
124
125
    /**
126
     * The mail that will be used in case of any errors like
127
     * - Remote mailserver down
128
     * - Remote user has exceeded his quota
129
     * - Unknown user
130
     *
131
     * @var array
132
     */
133
    protected $_returnPath = [];
134
135
    /**
136
     * Carbon Copy
137
     *
138
     * List of email's that should receive a copy of the email.
139
     * The Recipient WILL be able to see this list
140
     *
141
     * @var array
142
     */
143
    protected $_cc = [];
144
145
    /**
146
     * Blind Carbon Copy
147
     *
148
     * List of email's that should receive a copy of the email.
149
     * The Recipient WILL NOT be able to see this list
150
     *
151
     * @var array
152
     */
153
    protected $_bcc = [];
154
155
    /**
156
     * Message ID
157
     *
158
     * @var bool|string
159
     */
160
    protected $_messageId = true;
161
162
    /**
163
     * Domain for messageId generation.
164
     * Needs to be manually set for CLI mailing as env('HTTP_HOST') is empty
165
     *
166
     * @var string
167
     */
168
    protected $_domain;
169
170
    /**
171
     * The subject of the email
172
     *
173
     * @var string
174
     */
175
    protected $_subject = '';
176
177
    /**
178
     * Associative array of a user defined headers
179
     * Keys will be prefixed 'X-' as per RFC2822 Section 4.7.5
180
     *
181
     * @var array
182
     */
183
    protected $_headers = [];
184
185
    /**
186
     * Text message
187
     *
188
     * @var string
189
     */
190
    protected $_textMessage = '';
191
192
    /**
193
     * Html message
194
     *
195
     * @var string
196
     */
197
    protected $_htmlMessage = '';
198
199
    /**
200
     * Final message to send
201
     *
202
     * @var array
203
     */
204
    protected $_message = [];
205
206
    /**
207
     * Available formats to be sent.
208
     *
209
     * @var array
210
     */
211
    protected $_emailFormatAvailable = ['text', 'html', 'both'];
212
213
    /**
214
     * What format should the email be sent in
215
     *
216
     * @var string
217
     */
218
    protected $_emailFormat = 'text';
219
220
    /**
221
     * The transport instance to use for sending mail.
222
     *
223
     * @var \Cake\Mailer\AbstractTransport|null
224
     */
225
    protected $_transport;
226
227
    /**
228
     * Charset the email body is sent in
229
     *
230
     * @var string
231
     */
232
    public $charset = 'utf-8';
233
234
    /**
235
     * Charset the email header is sent in
236
     * If null, the $charset property will be used as default
237
     *
238
     * @var string|null
239
     */
240
    public $headerCharset;
241
242
    /**
243
     * The email transfer encoding used.
244
     * If null, the $charset property is used for determined the transfer encoding.
245
     *
246
     * @var string|null
247
     */
248
    protected $transferEncoding;
249
250
    /**
251
     * Available encoding to be set for transfer.
252
     *
253
     * @var array
254
     */
255
    protected $_transferEncodingAvailable = [
256
        '7bit',
257
        '8bit',
258
        'base64',
259
        'binary',
260
        'quoted-printable',
261
    ];
262
263
    /**
264
     * The application wide charset, used to encode headers and body
265
     *
266
     * @var string|null
267
     */
268
    protected $_appCharset;
269
270
    /**
271
     * List of files that should be attached to the email.
272
     *
273
     * Only absolute paths
274
     *
275
     * @var array
276
     */
277
    protected $_attachments = [];
278
279
    /**
280
     * If set, boundary to use for multipart mime messages
281
     *
282
     * @var string|null
283
     */
284
    protected $_boundary;
285
286
    /**
287
     * Contains the optional priority of the email.
288
     *
289
     * @var int|null
290
     */
291
    protected $_priority;
292
293
    /**
294
     * An array mapping url schemes to fully qualified Transport class names.
295
     * Unused.
296
     *
297
     * @var string[]
298
     * @deprecated 3.7.0 This property is unused and will be removed in 4.0.0.
299
     */
300
    protected static $_dsnClassMap = [];
301
302
    /**
303
     * A copy of the configuration profile for this
304
     * instance. This copy can be modified with Email::profile().
305
     *
306
     * @var array
307
     */
308
    protected $_profile = [];
309
310
    /**
311
     * 8Bit character sets
312
     *
313
     * @var array
314
     */
315
    protected $_charset8bit = ['UTF-8', 'SHIFT_JIS'];
316
317
    /**
318
     * Define Content-Type charset name
319
     *
320
     * @var array
321
     */
322
    protected $_contentTypeCharset = [
323
        'ISO-2022-JP-MS' => 'ISO-2022-JP',
324
    ];
325
326
    /**
327
     * Regex for email validation
328
     *
329
     * If null, filter_var() will be used. Use the emailPattern() method
330
     * to set a custom pattern.'
331
     *
332
     * @var string
333
     */
334
    protected $_emailPattern = self::EMAIL_PATTERN;
335
336
    /**
337
     * Constructor
338
     *
339
     * @param array|string|null $config Array of configs, or string to load configs from app.php
340
     */
341
    public function __construct($config = null)
342
    {
343
        $this->_appCharset = Configure::read('App.encoding');
344
        if ($this->_appCharset !== null) {
345
            $this->charset = $this->_appCharset;
346
        }
347
        $this->_domain = preg_replace('/\:\d+$/', '', env('HTTP_HOST'));
348
        if (empty($this->_domain)) {
349
            $this->_domain = php_uname('n');
350
        }
351
352
        $this->viewBuilder()
353
            ->setClassName('Cake\View\View')
354
            ->setTemplate('')
355
            ->setLayout('default')
356
            ->setHelpers(['Html']);
357
358
        if ($config === null) {
359
            $config = static::getConfig('default');
360
        }
361
        if ($config) {
362
            $this->setProfile($config);
363
        }
364
        if (empty($this->headerCharset)) {
365
            $this->headerCharset = $this->charset;
366
        }
367
    }
368
369
    /**
370
     * Clone ViewBuilder instance when email object is cloned.
371
     *
372
     * @return void
373
     */
374
    public function __clone()
375
    {
376
        $this->_viewBuilder = clone $this->viewBuilder();
377
    }
378
379
    /**
380
     * Sets "from" address.
381
     *
382
     * @param string|array $email Null to get, String with email,
383
     *   Array with email as key, name as value or email as value (without name)
384
     * @param string|null $name Name
385
     * @return $this
386
     * @throws \InvalidArgumentException
387
     */
388
    public function setFrom($email, $name = null)
389
    {
390
        return $this->_setEmailSingle('_from', $email, $name, 'From requires only 1 email address.');
391
    }
392
393
    /**
394
     * Gets "from" address.
395
     *
396
     * @return array
397
     */
398
    public function getFrom()
399
    {
400
        return $this->_from;
401
    }
402
403
    /**
404
     * From
405
     *
406
     * @deprecated 3.4.0 Use setFrom()/getFrom() instead.
407
     * @param string|array|null $email Null to get, String with email,
408
     *   Array with email as key, name as value or email as value (without name)
409
     * @param string|null $name Name
410
     * @return array|$this
411
     * @throws \InvalidArgumentException
412
     */
413
    public function from($email = null, $name = null)
414
    {
415
        deprecationWarning('Email::from() is deprecated. Use Email::setFrom() or Email::getFrom() instead.');
416
        if ($email === null) {
417
            return $this->getFrom();
418
        }
419
420
        return $this->setFrom($email, $name);
421
    }
422
423
    /**
424
     * Sets "sender" address.
425
     *
426
     * @param string|array $email String with email,
427
     *   Array with email as key, name as value or email as value (without name)
428
     * @param string|null $name Name
429
     * @return $this
430
     * @throws \InvalidArgumentException
431
     */
432
    public function setSender($email, $name = null)
433
    {
434
        return $this->_setEmailSingle('_sender', $email, $name, 'Sender requires only 1 email address.');
435
    }
436
437
    /**
438
     * Gets "sender" address.
439
     *
440
     * @return array
441
     */
442
    public function getSender()
443
    {
444
        return $this->_sender;
445
    }
446
447
    /**
448
     * Sender
449
     *
450
     * @deprecated 3.4.0 Use setSender()/getSender() instead.
451
     * @param string|array|null $email Null to get, String with email,
452
     *   Array with email as key, name as value or email as value (without name)
453
     * @param string|null $name Name
454
     * @return array|$this
455
     * @throws \InvalidArgumentException
456
     */
457
    public function sender($email = null, $name = null)
458
    {
459
        deprecationWarning('Email::sender() is deprecated. Use Email::setSender() or Email::getSender() instead.');
460
461
        if ($email === null) {
462
            return $this->getSender();
463
        }
464
465
        return $this->setSender($email, $name);
466
    }
467
468
    /**
469
     * Sets "Reply-To" address.
470
     *
471
     * @param string|array $email String with email,
472
     *   Array with email as key, name as value or email as value (without name)
473
     * @param string|null $name Name
474
     * @return $this
475
     * @throws \InvalidArgumentException
476
     */
477
    public function setReplyTo($email, $name = null)
478
    {
479
        return $this->_setEmailSingle('_replyTo', $email, $name, 'Reply-To requires only 1 email address.');
480
    }
481
482
    /**
483
     * Gets "Reply-To" address.
484
     *
485
     * @return array
486
     */
487
    public function getReplyTo()
488
    {
489
        return $this->_replyTo;
490
    }
491
492
    /**
493
     * Reply-To
494
     *
495
     * @deprecated 3.4.0 Use setReplyTo()/getReplyTo() instead.
496
     * @param string|array|null $email Null to get, String with email,
497
     *   Array with email as key, name as value or email as value (without name)
498
     * @param string|null $name Name
499
     * @return array|$this
500
     * @throws \InvalidArgumentException
501
     */
502
    public function replyTo($email = null, $name = null)
503
    {
504
        deprecationWarning('Email::replyTo() is deprecated. Use Email::setReplyTo() or Email::getReplyTo() instead.');
505
506
        if ($email === null) {
507
            return $this->getReplyTo();
508
        }
509
510
        return $this->setReplyTo($email, $name);
511
    }
512
513
    /**
514
     * Sets Read Receipt (Disposition-Notification-To header).
515
     *
516
     * @param string|array $email String with email,
517
     *   Array with email as key, name as value or email as value (without name)
518
     * @param string|null $name Name
519
     * @return $this
520
     * @throws \InvalidArgumentException
521
     */
522
    public function setReadReceipt($email, $name = null)
523
    {
524
        return $this->_setEmailSingle('_readReceipt', $email, $name, 'Disposition-Notification-To requires only 1 email address.');
525
    }
526
527
    /**
528
     * Gets Read Receipt (Disposition-Notification-To header).
529
     *
530
     * @return array
531
     */
532
    public function getReadReceipt()
533
    {
534
        return $this->_readReceipt;
535
    }
536
537
    /**
538
     * Read Receipt (Disposition-Notification-To header)
539
     *
540
     * @deprecated 3.4.0 Use setReadReceipt()/getReadReceipt() instead.
541
     * @param string|array|null $email Null to get, String with email,
542
     *   Array with email as key, name as value or email as value (without name)
543
     * @param string|null $name Name
544
     * @return array|$this
545
     * @throws \InvalidArgumentException
546
     */
547
    public function readReceipt($email = null, $name = null)
548
    {
549
        deprecationWarning('Email::readReceipt() is deprecated. Use Email::setReadReceipt() or Email::getReadReceipt() instead.');
550
551
        if ($email === null) {
552
            return $this->getReadReceipt();
553
        }
554
555
        return $this->setReadReceipt($email, $name);
556
    }
557
558
    /**
559
     * Return Path
560
     *
561
     * @param string|array $email String with email,
562
     *   Array with email as key, name as value or email as value (without name)
563
     * @param string|null $name Name
564
     * @return $this
565
     * @throws \InvalidArgumentException
566
     */
567
    public function setReturnPath($email, $name = null)
568
    {
569
        return $this->_setEmailSingle('_returnPath', $email, $name, 'Return-Path requires only 1 email address.');
570
    }
571
572
    /**
573
     * Gets return path.
574
     *
575
     * @return array
576
     */
577
    public function getReturnPath()
578
    {
579
        return $this->_returnPath;
580
    }
581
582
    /**
583
     * Return Path
584
     *
585
     * @deprecated 3.4.0 Use setReturnPath()/getReturnPath() instead.
586
     * @param string|array|null $email Null to get, String with email,
587
     *   Array with email as key, name as value or email as value (without name)
588
     * @param string|null $name Name
589
     * @return array|$this
590
     * @throws \InvalidArgumentException
591
     */
592
    public function returnPath($email = null, $name = null)
593
    {
594
        deprecationWarning('Email::returnPath() is deprecated. Use Email::setReturnPath() or Email::getReturnPath() instead.');
595
        if ($email === null) {
596
            return $this->getReturnPath();
597
        }
598
599
        return $this->setReturnPath($email, $name);
600
    }
601
602
    /**
603
     * Sets "to" address.
604
     *
605
     * @param string|array $email String with email,
606
     *   Array with email as key, name as value or email as value (without name)
607
     * @param string|null $name Name
608
     * @return $this
609
     */
610
    public function setTo($email, $name = null)
611
    {
612
        return $this->_setEmail('_to', $email, $name);
613
    }
614
615
    /**
616
     * Gets "to" address
617
     *
618
     * @return array
619
     */
620
    public function getTo()
621
    {
622
        return $this->_to;
623
    }
624
625
    /**
626
     * To
627
     *
628
     * @deprecated 3.4.0 Use setTo()/getTo() instead.
629
     * @param string|array|null $email Null to get, String with email,
630
     *   Array with email as key, name as value or email as value (without name)
631
     * @param string|null $name Name
632
     * @return array|$this
633
     */
634
    public function to($email = null, $name = null)
635
    {
636
        deprecationWarning('Email::to() is deprecated. Use Email::setTo() or Email::getTo() instead.');
637
638
        if ($email === null) {
639
            return $this->getTo();
640
        }
641
642
        return $this->setTo($email, $name);
643
    }
644
645
    /**
646
     * Add To
647
     *
648
     * @param string|array $email Null to get, String with email,
649
     *   Array with email as key, name as value or email as value (without name)
650
     * @param string|null $name Name
651
     * @return $this
652
     */
653
    public function addTo($email, $name = null)
654
    {
655
        return $this->_addEmail('_to', $email, $name);
656
    }
657
658
    /**
659
     * Sets "cc" address.
660
     *
661
     * @param string|array $email String with email,
662
     *   Array with email as key, name as value or email as value (without name)
663
     * @param string|null $name Name
664
     * @return $this
665
     */
666
    public function setCc($email, $name = null)
667
    {
668
        return $this->_setEmail('_cc', $email, $name);
669
    }
670
671
    /**
672
     * Gets "cc" address.
673
     *
674
     * @return array
675
     */
676
    public function getCc()
677
    {
678
        return $this->_cc;
679
    }
680
681
    /**
682
     * Cc
683
     *
684
     * @deprecated 3.4.0 Use setCc()/getCc() instead.
685
     * @param string|array|null $email Null to get, String with email,
686
     *   Array with email as key, name as value or email as value (without name)
687
     * @param string|null $name Name
688
     * @return array|$this
689
     */
690
    public function cc($email = null, $name = null)
691
    {
692
        deprecationWarning('Email::cc() is deprecated. Use Email::setCc() or Email::getCc() instead.');
693
694
        if ($email === null) {
695
            return $this->getCc();
696
        }
697
698
        return $this->setCc($email, $name);
699
    }
700
701
    /**
702
     * Add Cc
703
     *
704
     * @param string|array $email Null to get, String with email,
705
     *   Array with email as key, name as value or email as value (without name)
706
     * @param string|null $name Name
707
     * @return $this
708
     */
709
    public function addCc($email, $name = null)
710
    {
711
        return $this->_addEmail('_cc', $email, $name);
712
    }
713
714
    /**
715
     * Sets "bcc" address.
716
     *
717
     * @param string|array $email String with email,
718
     *   Array with email as key, name as value or email as value (without name)
719
     * @param string|null $name Name
720
     * @return $this
721
     */
722
    public function setBcc($email, $name = null)
723
    {
724
        return $this->_setEmail('_bcc', $email, $name);
725
    }
726
727
    /**
728
     * Gets "bcc" address.
729
     *
730
     * @return array
731
     */
732
    public function getBcc()
733
    {
734
        return $this->_bcc;
735
    }
736
737
    /**
738
     * Bcc
739
     *
740
     * @deprecated 3.4.0 Use setBcc()/getBcc() instead.
741
     * @param string|array|null $email Null to get, String with email,
742
     *   Array with email as key, name as value or email as value (without name)
743
     * @param string|null $name Name
744
     * @return array|$this
745
     */
746
    public function bcc($email = null, $name = null)
747
    {
748
        deprecationWarning('Email::bcc() is deprecated. Use Email::setBcc() or Email::getBcc() instead.');
749
750
        if ($email === null) {
751
            return $this->getBcc();
752
        }
753
754
        return $this->setBcc($email, $name);
755
    }
756
757
    /**
758
     * Add Bcc
759
     *
760
     * @param string|array $email Null to get, String with email,
761
     *   Array with email as key, name as value or email as value (without name)
762
     * @param string|null $name Name
763
     * @return $this
764
     */
765
    public function addBcc($email, $name = null)
766
    {
767
        return $this->_addEmail('_bcc', $email, $name);
768
    }
769
770
    /**
771
     * Charset setter.
772
     *
773
     * @param string|null $charset Character set.
774
     * @return $this
775
     */
776
    public function setCharset($charset)
777
    {
778
        $this->charset = $charset;
779
        if (!$this->headerCharset) {
780
            $this->headerCharset = $charset;
781
        }
782
783
        return $this;
784
    }
785
786
    /**
787
     * Charset getter.
788
     *
789
     * @return string Charset
790
     */
791
    public function getCharset()
792
    {
793
        return $this->charset;
794
    }
795
796
    /**
797
     * Charset setter/getter
798
     *
799
     * @deprecated 3.4.0 Use setCharset()/getCharset() instead.
800
     * @param string|null $charset Character set.
801
     * @return string Charset
802
     */
803
    public function charset($charset = null)
804
    {
805
        deprecationWarning('Email::charset() is deprecated. Use Email::setCharset() or Email::getCharset() instead.');
806
807
        if ($charset === null) {
808
            return $this->getCharset();
809
        }
810
        $this->setCharset($charset);
811
812
        return $this->charset;
813
    }
814
815
    /**
816
     * HeaderCharset setter.
817
     *
818
     * @param string|null $charset Character set.
819
     * @return $this
820
     */
821
    public function setHeaderCharset($charset)
822
    {
823
        $this->headerCharset = $charset;
824
825
        return $this;
826
    }
827
828
    /**
829
     * HeaderCharset getter.
830
     *
831
     * @return string Charset
832
     */
833
    public function getHeaderCharset()
834
    {
835
        return $this->headerCharset ? $this->headerCharset : $this->charset;
836
    }
837
838
    /**
839
     * HeaderCharset setter/getter
840
     *
841
     * @deprecated 3.4.0 Use setHeaderCharset()/getHeaderCharset() instead.
842
     * @param string|null $charset Character set.
843
     * @return string Charset
844
     */
845
    public function headerCharset($charset = null)
846
    {
847
        deprecationWarning('Email::headerCharset() is deprecated. Use Email::setHeaderCharset() or Email::getHeaderCharset() instead.');
848
849
        if ($charset === null) {
850
            return $this->getHeaderCharset();
851
        }
852
853
        $this->setHeaderCharset($charset);
854
855
        return $this->headerCharset;
856
    }
857
858
    /**
859
     * TransferEncoding setter.
860
     *
861
     * @param string|null $encoding Encoding set.
862
     * @return $this
863
     */
864
    public function setTransferEncoding($encoding)
865
    {
866
        $encoding = strtolower($encoding);
867
        if (!in_array($encoding, $this->_transferEncodingAvailable)) {
868
            throw new InvalidArgumentException(
869
                sprintf(
870
                    'Transfer encoding not available. Can be : %s.',
871
                    implode(', ', $this->_transferEncodingAvailable)
872
                )
873
            );
874
        }
875
        $this->transferEncoding = $encoding;
876
877
        return $this;
878
    }
879
880
    /**
881
     * TransferEncoding getter.
882
     *
883
     * @return string|null Encoding
884
     */
885
    public function getTransferEncoding()
886
    {
887
        return $this->transferEncoding;
888
    }
889
890
    /**
891
     * EmailPattern setter/getter
892
     *
893
     * @param string|null $regex The pattern to use for email address validation,
894
     *   null to unset the pattern and make use of filter_var() instead.
895
     * @return $this
896
     */
897
    public function setEmailPattern($regex)
898
    {
899
        $this->_emailPattern = $regex;
900
901
        return $this;
902
    }
903
904
    /**
905
     * EmailPattern setter/getter
906
     *
907
     * @return string
908
     */
909
    public function getEmailPattern()
910
    {
911
        return $this->_emailPattern;
912
    }
913
914
    /**
915
     * EmailPattern setter/getter
916
     *
917
     * @deprecated 3.4.0 Use setEmailPattern()/getEmailPattern() instead.
918
     * @param string|bool|null $regex The pattern to use for email address validation,
919
     *   null to unset the pattern and make use of filter_var() instead, false or
920
     *   nothing to return the current value
921
     * @return string|$this
922
     */
923
    public function emailPattern($regex = false)
924
    {
925
        deprecationWarning('Email::emailPattern() is deprecated. Use Email::setEmailPattern() or Email::getEmailPattern() instead.');
926
927
        if ($regex === false) {
928
            return $this->getEmailPattern();
929
        }
930
931
        return $this->setEmailPattern($regex);
0 ignored issues
show
Bug introduced by
It seems like $regex defined by parameter $regex on line 923 can also be of type boolean; however, Cake\Mailer\Email::setEmailPattern() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
932
    }
933
934
    /**
935
     * Set email
936
     *
937
     * @param string $varName Property name
938
     * @param string|array $email String with email,
939
     *   Array with email as key, name as value or email as value (without name)
940
     * @param string $name Name
941
     * @return $this
942
     * @throws \InvalidArgumentException
943
     */
944 View Code Duplication
    protected function _setEmail($varName, $email, $name)
945
    {
946
        if (!is_array($email)) {
947
            $this->_validateEmail($email, $varName);
948
            if ($name === null) {
949
                $name = $email;
950
            }
951
            $this->{$varName} = [$email => $name];
952
953
            return $this;
954
        }
955
        $list = [];
956
        foreach ($email as $key => $value) {
957
            if (is_int($key)) {
958
                $key = $value;
959
            }
960
            $this->_validateEmail($key, $varName);
961
            $list[$key] = $value;
962
        }
963
        $this->{$varName} = $list;
964
965
        return $this;
966
    }
967
968
    /**
969
     * Validate email address
970
     *
971
     * @param string $email Email address to validate
972
     * @param string $context Which property was set
973
     * @return void
974
     * @throws \InvalidArgumentException If email address does not validate
975
     */
976
    protected function _validateEmail($email, $context)
977
    {
978
        if ($this->_emailPattern === null) {
979
            if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
980
                return;
981
            }
982
        } elseif (preg_match($this->_emailPattern, $email)) {
983
            return;
984
        }
985
986
        $context = ltrim($context, '_');
987
        if ($email == '') {
988
            throw new InvalidArgumentException(sprintf('The email set for "%s" is empty.', $context));
989
        }
990
        throw new InvalidArgumentException(sprintf('Invalid email set for "%s". You passed "%s".', $context, $email));
991
    }
992
993
    /**
994
     * Set only 1 email
995
     *
996
     * @param string $varName Property name
997
     * @param string|array $email String with email,
998
     *   Array with email as key, name as value or email as value (without name)
999
     * @param string $name Name
1000
     * @param string $throwMessage Exception message
1001
     * @return $this
1002
     * @throws \InvalidArgumentException
1003
     */
1004
    protected function _setEmailSingle($varName, $email, $name, $throwMessage)
1005
    {
1006
        if ($email === []) {
1007
            $this->{$varName} = $email;
1008
1009
            return $this;
1010
        }
1011
1012
        $current = $this->{$varName};
1013
        $this->_setEmail($varName, $email, $name);
1014
        if (count($this->{$varName}) !== 1) {
1015
            $this->{$varName} = $current;
1016
            throw new InvalidArgumentException($throwMessage);
1017
        }
1018
1019
        return $this;
1020
    }
1021
1022
    /**
1023
     * Add email
1024
     *
1025
     * @param string $varName Property name
1026
     * @param string|array $email String with email,
1027
     *   Array with email as key, name as value or email as value (without name)
1028
     * @param string $name Name
1029
     * @return $this
1030
     * @throws \InvalidArgumentException
1031
     */
1032 View Code Duplication
    protected function _addEmail($varName, $email, $name)
1033
    {
1034
        if (!is_array($email)) {
1035
            $this->_validateEmail($email, $varName);
1036
            if ($name === null) {
1037
                $name = $email;
1038
            }
1039
            $this->{$varName}[$email] = $name;
1040
1041
            return $this;
1042
        }
1043
        $list = [];
1044
        foreach ($email as $key => $value) {
1045
            if (is_int($key)) {
1046
                $key = $value;
1047
            }
1048
            $this->_validateEmail($key, $varName);
1049
            $list[$key] = $value;
1050
        }
1051
        $this->{$varName} = array_merge($this->{$varName}, $list);
1052
1053
        return $this;
1054
    }
1055
1056
    /**
1057
     * Sets subject.
1058
     *
1059
     * @param string $subject Subject string.
1060
     * @return $this
1061
     */
1062
    public function setSubject($subject)
1063
    {
1064
        $this->_subject = $this->_encode((string)$subject);
1065
1066
        return $this;
1067
    }
1068
1069
    /**
1070
     * Gets subject.
1071
     *
1072
     * @return string
1073
     */
1074
    public function getSubject()
1075
    {
1076
        return $this->_subject;
1077
    }
1078
1079
    /**
1080
     * Get/Set Subject.
1081
     *
1082
     * @deprecated 3.4.0 Use setSubject()/getSubject() instead.
1083
     * @param string|null $subject Subject string.
1084
     * @return string|$this
1085
     */
1086
    public function subject($subject = null)
1087
    {
1088
        deprecationWarning('Email::subject() is deprecated. Use Email::setSubject() or Email::getSubject() instead.');
1089
1090
        if ($subject === null) {
1091
            return $this->getSubject();
1092
        }
1093
1094
        return $this->setSubject($subject);
1095
    }
1096
1097
    /**
1098
     * Get original subject without encoding
1099
     *
1100
     * @return string Original subject
1101
     */
1102
    public function getOriginalSubject()
1103
    {
1104
        return $this->_decode($this->_subject);
1105
    }
1106
1107
    /**
1108
     * Sets headers for the message
1109
     *
1110
     * @param array $headers Associative array containing headers to be set.
1111
     * @return $this
1112
     */
1113
    public function setHeaders(array $headers)
1114
    {
1115
        $this->_headers = $headers;
1116
1117
        return $this;
1118
    }
1119
1120
    /**
1121
     * Add header for the message
1122
     *
1123
     * @param array $headers Headers to set.
1124
     * @return $this
1125
     */
1126
    public function addHeaders(array $headers)
1127
    {
1128
        $this->_headers = Hash::merge($this->_headers, $headers);
1129
1130
        return $this;
1131
    }
1132
1133
    /**
1134
     * Get list of headers
1135
     *
1136
     * ### Includes:
1137
     *
1138
     * - `from`
1139
     * - `replyTo`
1140
     * - `readReceipt`
1141
     * - `returnPath`
1142
     * - `to`
1143
     * - `cc`
1144
     * - `bcc`
1145
     * - `subject`
1146
     *
1147
     * @param array $include List of headers.
1148
     * @return array
1149
     */
1150
    public function getHeaders(array $include = [])
1151
    {
1152
        if ($include == array_values($include)) {
1153
            $include = array_fill_keys($include, true);
1154
        }
1155
        $defaults = array_fill_keys(
1156
            [
1157
                'from', 'sender', 'replyTo', 'readReceipt', 'returnPath',
1158
                'to', 'cc', 'bcc', 'subject'],
1159
            false
1160
        );
1161
        $include += $defaults;
1162
1163
        $headers = [];
1164
        $relation = [
1165
            'from' => 'From',
1166
            'replyTo' => 'Reply-To',
1167
            'readReceipt' => 'Disposition-Notification-To',
1168
            'returnPath' => 'Return-Path',
1169
        ];
1170
        foreach ($relation as $var => $header) {
1171
            if ($include[$var]) {
1172
                $var = '_' . $var;
1173
                $headers[$header] = current($this->_formatAddress($this->{$var}));
1174
            }
1175
        }
1176
        if ($include['sender']) {
1177
            if (key($this->_sender) === key($this->_from)) {
1178
                $headers['Sender'] = '';
1179
            } else {
1180
                $headers['Sender'] = current($this->_formatAddress($this->_sender));
1181
            }
1182
        }
1183
1184
        foreach (['to', 'cc', 'bcc'] as $var) {
1185
            if ($include[$var]) {
1186
                $classVar = '_' . $var;
1187
                $headers[ucfirst($var)] = implode(', ', $this->_formatAddress($this->{$classVar}));
1188
            }
1189
        }
1190
1191
        $headers += $this->_headers;
1192
        if (!isset($headers['Date'])) {
1193
            $headers['Date'] = date(DATE_RFC2822);
1194
        }
1195
        if ($this->_messageId !== false) {
1196
            if ($this->_messageId === true) {
1197
                $this->_messageId = '<' . str_replace('-', '', Text::uuid()) . '@' . $this->_domain . '>';
1198
            }
1199
1200
            $headers['Message-ID'] = $this->_messageId;
1201
        }
1202
1203
        if ($this->_priority) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_priority of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1204
            $headers['X-Priority'] = $this->_priority;
1205
        }
1206
1207
        if ($include['subject']) {
1208
            $headers['Subject'] = $this->_subject;
1209
        }
1210
1211
        $headers['MIME-Version'] = '1.0';
1212
        if ($this->_attachments) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_attachments of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1213
            $headers['Content-Type'] = 'multipart/mixed; boundary="' . $this->_boundary . '"';
1214
        } elseif ($this->_emailFormat === 'both') {
1215
            $headers['Content-Type'] = 'multipart/alternative; boundary="' . $this->_boundary . '"';
1216
        } elseif ($this->_emailFormat === 'text') {
1217
            $headers['Content-Type'] = 'text/plain; charset=' . $this->_getContentTypeCharset();
1218
        } elseif ($this->_emailFormat === 'html') {
1219
            $headers['Content-Type'] = 'text/html; charset=' . $this->_getContentTypeCharset();
1220
        }
1221
        $headers['Content-Transfer-Encoding'] = $this->_getContentTransferEncoding();
1222
1223
        return $headers;
1224
    }
1225
1226
    /**
1227
     * Format addresses
1228
     *
1229
     * If the address contains non alphanumeric/whitespace characters, it will
1230
     * be quoted as characters like `:` and `,` are known to cause issues
1231
     * in address header fields.
1232
     *
1233
     * @param array $address Addresses to format.
1234
     * @return array
1235
     */
1236
    protected function _formatAddress($address)
1237
    {
1238
        $return = [];
1239
        foreach ($address as $email => $alias) {
1240
            if ($email === $alias) {
1241
                $return[] = $email;
1242
            } else {
1243
                $encoded = $this->_encode($alias);
1244
                if ($encoded === $alias && preg_match('/[^a-z0-9 ]/i', $encoded)) {
1245
                    $encoded = '"' . str_replace('"', '\"', $encoded) . '"';
1246
                }
1247
                $return[] = sprintf('%s <%s>', $encoded, $email);
1248
            }
1249
        }
1250
1251
        return $return;
1252
    }
1253
1254
    /**
1255
     * Sets template.
1256
     *
1257
     * @param string|null $template Template name or null to not use.
1258
     * @return $this
1259
     * @deprecated 3.7.0 Use $email->viewBuilder()->setTemplate() instead.
1260
     */
1261
    public function setTemplate($template)
1262
    {
1263
        deprecationWarning(
1264
            'Email::setTemplate() is deprecated. Use $email->viewBuilder()->setTemplate() instead.'
1265
        );
1266
1267
        $this->viewBuilder()->setTemplate($template ?: '');
1268
1269
        return $this;
1270
    }
1271
1272
    /**
1273
     * Gets template.
1274
     *
1275
     * @return string
1276
     * @deprecated 3.7.0 Use $email->viewBuilder()->getTemplate() instead.
1277
     */
1278
    public function getTemplate()
1279
    {
1280
        deprecationWarning(
1281
            'Email::getTemplate() is deprecated. Use $email->viewBuilder()->getTemplate() instead.'
1282
        );
1283
1284
        return $this->viewBuilder()->getTemplate();
1285
    }
1286
1287
    /**
1288
     * Sets layout.
1289
     *
1290
     * @param string|null $layout Layout name or null to not use
1291
     * @return $this
1292
     * @deprecated 3.7.0 Use $email->viewBuilder()->setLayout() instead.
1293
     */
1294
    public function setLayout($layout)
1295
    {
1296
        deprecationWarning(
1297
            'Email::setLayout() is deprecated. Use $email->viewBuilder()->setLayout() instead.'
1298
        );
1299
1300
        $this->viewBuilder()->setLayout($layout ?: false);
1301
1302
        return $this;
1303
    }
1304
1305
    /**
1306
     * Gets layout.
1307
     *
1308
     * @deprecated 3.7.0 Use $email->viewBuilder()->getLayout() instead.
1309
     * @return string
1310
     */
1311
    public function getLayout()
1312
    {
1313
        deprecationWarning(
1314
            'Email::getLayout() is deprecated. Use $email->viewBuilder()->getLayout() instead.'
1315
        );
1316
1317
        return $this->viewBuilder()->getLayout();
1318
    }
1319
1320
    /**
1321
     * Template and layout
1322
     *
1323
     * @deprecated 3.4.0 Use setTemplate()/getTemplate() and setLayout()/getLayout() instead.
1324
     * @param bool|string $template Template name or null to not use
1325
     * @param bool|string $layout Layout name or null to not use
1326
     * @return array|$this
1327
     */
1328
    public function template($template = false, $layout = false)
1329
    {
1330
        deprecationWarning(
1331
            'Email::template() is deprecated. ' .
1332
            'Use $email->viewBuilder()->getTemplate()/setTemplate() ' .
1333
            'and $email->viewBuilder()->getLayout()/setLayout() instead.'
1334
        );
1335
1336
        if ($template === false) {
1337
            return [
1338
                'template' => $this->viewBuilder()->getTemplate(),
1339
                'layout' => $this->viewBuilder()->getLayout(),
1340
            ];
1341
        }
1342
        $this->viewBuilder()->setTemplate($template);
0 ignored issues
show
Bug introduced by
It seems like $template defined by parameter $template on line 1328 can also be of type boolean; however, Cake\View\ViewBuilder::setTemplate() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1343
        if ($layout !== false) {
1344
            $this->viewBuilder()->setLayout($layout);
0 ignored issues
show
Bug introduced by
It seems like $layout defined by parameter $layout on line 1328 can also be of type boolean; however, Cake\View\ViewBuilder::setLayout() does only seem to accept string|false|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1345
        }
1346
1347
        return $this;
1348
    }
1349
1350
    /**
1351
     * Sets view class for render.
1352
     *
1353
     * @param string $viewClass View class name.
1354
     * @return $this
1355
     */
1356
    public function setViewRenderer($viewClass)
1357
    {
1358
        $this->viewBuilder()->setClassName($viewClass);
1359
1360
        return $this;
1361
    }
1362
1363
    /**
1364
     * Gets view class for render.
1365
     *
1366
     * @return string
1367
     */
1368
    public function getViewRenderer()
1369
    {
1370
        return $this->viewBuilder()->getClassName();
1371
    }
1372
1373
    /**
1374
     * View class for render
1375
     *
1376
     * @deprecated 3.4.0 Use setViewRenderer()/getViewRenderer() instead.
1377
     * @param string|null $viewClass View class name.
1378
     * @return string|$this
1379
     */
1380
    public function viewRender($viewClass = null)
1381
    {
1382
        deprecationWarning('Email::viewRender() is deprecated. Use Email::setViewRenderer() or Email::getViewRenderer() instead.');
1383
1384
        if ($viewClass === null) {
1385
            return $this->getViewRenderer();
1386
        }
1387
        $this->setViewRenderer($viewClass);
1388
1389
        return $this;
1390
    }
1391
1392
    /**
1393
     * Sets variables to be set on render.
1394
     *
1395
     * @param array $viewVars Variables to set for view.
1396
     * @return $this
1397
     */
1398
    public function setViewVars($viewVars)
1399
    {
1400
        $this->set((array)$viewVars);
1401
1402
        return $this;
1403
    }
1404
1405
    /**
1406
     * Gets variables to be set on render.
1407
     *
1408
     * @return array
1409
     */
1410
    public function getViewVars()
1411
    {
1412
        return $this->viewVars;
1413
    }
1414
1415
    /**
1416
     * Variables to be set on render
1417
     *
1418
     * @deprecated 3.4.0 Use setViewVars()/getViewVars() instead.
1419
     * @param array|null $viewVars Variables to set for view.
1420
     * @return array|$this
1421
     */
1422
    public function viewVars($viewVars = null)
1423
    {
1424
        deprecationWarning('Email::viewVars() is deprecated. Use Email::setViewVars() or Email::getViewVars() instead.');
1425
1426
        if ($viewVars === null) {
1427
            return $this->getViewVars();
1428
        }
1429
1430
        return $this->setViewVars($viewVars);
1431
    }
1432
1433
    /**
1434
     * Sets theme to use when rendering.
1435
     *
1436
     * @param string $theme Theme name.
1437
     * @return $this
1438
     * @deprecated 3.7.0 Use $email->viewBuilder()->setTheme() instead.
1439
     */
1440
    public function setTheme($theme)
1441
    {
1442
        deprecationWarning(
1443
            'Email::setTheme() is deprecated. Use $email->viewBuilder()->setTheme() instead.'
1444
        );
1445
1446
        $this->viewBuilder()->setTheme($theme);
1447
1448
        return $this;
1449
    }
1450
1451
    /**
1452
     * Gets theme to use when rendering.
1453
     *
1454
     * @return string
1455
     * @deprecated 3.7.0 Use $email->viewBuilder()->getTheme() instead.
1456
     */
1457
    public function getTheme()
1458
    {
1459
        deprecationWarning(
1460
            'Email::getTheme() is deprecated. Use $email->viewBuilder()->getTheme() instead.'
1461
        );
1462
1463
        return $this->viewBuilder()->getTheme();
1464
    }
1465
1466
    /**
1467
     * Theme to use when rendering
1468
     *
1469
     * @deprecated 3.4.0 Use setTheme()/getTheme() instead.
1470
     * @param string|null $theme Theme name.
1471
     * @return string|$this
1472
     */
1473 View Code Duplication
    public function theme($theme = null)
1474
    {
1475
        deprecationWarning(
1476
            'Email::theme() is deprecated. Use $email->viewBuilder()->getTheme()/setTheme() instead.'
1477
        );
1478
1479
        if ($theme === null) {
1480
            return $this->viewBuilder()->getTheme();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $this->viewBuilder()->getTheme(); of type string|false|null adds false to the return on line 1480 which is incompatible with the return type documented by Cake\Mailer\Email::theme of type string|Cake\Mailer\Email. It seems like you forgot to handle an error condition.
Loading history...
1481
        }
1482
1483
        $this->viewBuilder()->setTheme($theme);
1484
1485
        return $this;
1486
    }
1487
1488
    /**
1489
     * Sets helpers to be used when rendering.
1490
     *
1491
     * @param array $helpers Helpers list.
1492
     * @return $this
1493
     * @deprecated 3.7.0 Use $email->viewBuilder()->setHelpers() instead.
1494
     */
1495
    public function setHelpers(array $helpers)
1496
    {
1497
        deprecationWarning(
1498
            'Email::setHelpers() is deprecated. Use $email->viewBuilder()->setHelpers() instead.'
1499
        );
1500
1501
        $this->viewBuilder()->setHelpers($helpers, false);
1502
1503
        return $this;
1504
    }
1505
1506
    /**
1507
     * Gets helpers to be used when rendering.
1508
     *
1509
     * @return array
1510
     * @deprecated 3.7.0 Use $email->viewBuilder()->getHelpers() instead.
1511
     */
1512
    public function getHelpers()
1513
    {
1514
        deprecationWarning(
1515
            'Email::getHelpers() is deprecated. Use $email->viewBuilder()->getHelpers() instead.'
1516
        );
1517
1518
        return $this->viewBuilder()->getHelpers();
1519
    }
1520
1521
    /**
1522
     * Helpers to be used in render
1523
     *
1524
     * @deprecated 3.4.0 Use setHelpers()/getHelpers() instead.
1525
     * @param array|null $helpers Helpers list.
1526
     * @return array|$this
1527
     */
1528
    public function helpers($helpers = null)
1529
    {
1530
        deprecationWarning(
1531
            'Email::helpers() is deprecated. Use $email->viewBuilder()->getHelpers()/setHelpers() instead.'
1532
        );
1533
1534
        if ($helpers === null) {
1535
            return $this->viewBuilder()->getHelpers();
1536
        }
1537
1538
        $this->viewBuilder()->setHelpers((array)$helpers);
1539
1540
        return $this;
1541
    }
1542
1543
    /**
1544
     * Sets email format.
1545
     *
1546
     * @param string $format Formatting string.
1547
     * @return $this
1548
     * @throws \InvalidArgumentException
1549
     */
1550
    public function setEmailFormat($format)
1551
    {
1552
        if (!in_array($format, $this->_emailFormatAvailable)) {
1553
            throw new InvalidArgumentException('Format not available.');
1554
        }
1555
        $this->_emailFormat = $format;
1556
1557
        return $this;
1558
    }
1559
1560
    /**
1561
     * Gets email format.
1562
     *
1563
     * @return string
1564
     */
1565
    public function getEmailFormat()
1566
    {
1567
        return $this->_emailFormat;
1568
    }
1569
1570
    /**
1571
     * Email format
1572
     *
1573
     * @deprecated 3.4.0 Use setEmailFormat()/getEmailFormat() instead.
1574
     * @param string|null $format Formatting string.
1575
     * @return string|$this
1576
     * @throws \InvalidArgumentException
1577
     */
1578
    public function emailFormat($format = null)
1579
    {
1580
        deprecationWarning('Email::emailFormat() is deprecated. Use Email::setEmailFormat() or Email::getEmailFormat() instead.');
1581
1582
        if ($format === null) {
1583
            return $this->getEmailFormat();
1584
        }
1585
1586
        return $this->setEmailFormat($format);
1587
    }
1588
1589
    /**
1590
     * Sets the transport.
1591
     *
1592
     * When setting the transport you can either use the name
1593
     * of a configured transport or supply a constructed transport.
1594
     *
1595
     * @param string|\Cake\Mailer\AbstractTransport $name Either the name of a configured
1596
     *   transport, or a transport instance.
1597
     * @return $this
1598
     * @throws \LogicException When the chosen transport lacks a send method.
1599
     * @throws \InvalidArgumentException When $name is neither a string nor an object.
1600
     */
1601
    public function setTransport($name)
1602
    {
1603
        if (is_string($name)) {
1604
            $transport = TransportFactory::get($name);
1605
        } elseif (is_object($name)) {
1606
            $transport = $name;
1607
        } else {
1608
            throw new InvalidArgumentException(
1609
                sprintf('The value passed for the "$name" argument must be either a string, or an object, %s given.', gettype($name))
1610
            );
1611
        }
1612
        if (!method_exists($transport, 'send')) {
1613
            throw new LogicException(sprintf('The "%s" do not have send method.', get_class($transport)));
1614
        }
1615
1616
        $this->_transport = $transport;
1617
1618
        return $this;
1619
    }
1620
1621
    /**
1622
     * Gets the transport.
1623
     *
1624
     * @return \Cake\Mailer\AbstractTransport
1625
     */
1626
    public function getTransport()
1627
    {
1628
        return $this->_transport;
1629
    }
1630
1631
    /**
1632
     * Get/set the transport.
1633
     *
1634
     * When setting the transport you can either use the name
1635
     * of a configured transport or supply a constructed transport.
1636
     *
1637
     * @deprecated 3.4.0 Use setTransport()/getTransport() instead.
1638
     * @param string|\Cake\Mailer\AbstractTransport|null $name Either the name of a configured
1639
     *   transport, or a transport instance.
1640
     * @return \Cake\Mailer\AbstractTransport|$this
1641
     * @throws \LogicException When the chosen transport lacks a send method.
1642
     * @throws \InvalidArgumentException When $name is neither a string nor an object.
1643
     */
1644
    public function transport($name = null)
1645
    {
1646
        deprecationWarning('Email::transport() is deprecated. Use Email::setTransport() or Email::getTransport() instead.');
1647
1648
        if ($name === null) {
1649
            return $this->getTransport();
1650
        }
1651
1652
        return $this->setTransport($name);
1653
    }
1654
1655
    /**
1656
     * Sets message ID.
1657
     *
1658
     * @param bool|string $message True to generate a new Message-ID, False to ignore (not send in email), String to set as Message-ID.
1659
     * @return $this
1660
     * @throws \InvalidArgumentException
1661
     */
1662
    public function setMessageId($message)
1663
    {
1664
        if (is_bool($message)) {
1665
            $this->_messageId = $message;
1666
        } else {
1667
            if (!preg_match('/^\<.+@.+\>$/', $message)) {
1668
                throw new InvalidArgumentException('Invalid format to Message-ID. The text should be something like "<[email protected]>"');
1669
            }
1670
            $this->_messageId = $message;
1671
        }
1672
1673
        return $this;
1674
    }
1675
1676
    /**
1677
     * Gets message ID.
1678
     *
1679
     * @return bool|string
1680
     */
1681
    public function getMessageId()
1682
    {
1683
        return $this->_messageId;
1684
    }
1685
1686
    /**
1687
     * Message-ID
1688
     *
1689
     * @deprecated 3.4.0 Use setMessageId()/getMessageId() instead.
1690
     * @param bool|string|null $message True to generate a new Message-ID, False to ignore (not send in email), String to set as Message-ID
1691
     * @return bool|string|$this
1692
     * @throws \InvalidArgumentException
1693
     */
1694
    public function messageId($message = null)
1695
    {
1696
        deprecationWarning('Email::messageId() is deprecated. Use Email::setMessageId() or Email::getMessageId() instead.');
1697
1698
        if ($message === null) {
1699
            return $this->getMessageId();
1700
        }
1701
1702
        return $this->setMessageId($message);
1703
    }
1704
1705
    /**
1706
     * Sets domain.
1707
     *
1708
     * Domain as top level (the part after @).
1709
     *
1710
     * @param string $domain Manually set the domain for CLI mailing.
1711
     * @return $this
1712
     */
1713
    public function setDomain($domain)
1714
    {
1715
        $this->_domain = $domain;
1716
1717
        return $this;
1718
    }
1719
1720
    /**
1721
     * Gets domain.
1722
     *
1723
     * @return string
1724
     */
1725
    public function getDomain()
1726
    {
1727
        return $this->_domain;
1728
    }
1729
1730
    /**
1731
     * Domain as top level (the part after @)
1732
     *
1733
     * @deprecated 3.4.0 Use setDomain()/getDomain() instead.
1734
     * @param string|null $domain Manually set the domain for CLI mailing
1735
     * @return string|$this
1736
     */
1737
    public function domain($domain = null)
1738
    {
1739
        deprecationWarning('Email::domain() is deprecated. Use Email::setDomain() or Email::getDomain() instead.');
1740
1741
        if ($domain === null) {
1742
            return $this->getDomain();
1743
        }
1744
1745
        return $this->setDomain($domain);
1746
    }
1747
1748
    /**
1749
     * Add attachments to the email message
1750
     *
1751
     * Attachments can be defined in a few forms depending on how much control you need:
1752
     *
1753
     * Attach a single file:
1754
     *
1755
     * ```
1756
     * $email->setAttachments('path/to/file');
1757
     * ```
1758
     *
1759
     * Attach a file with a different filename:
1760
     *
1761
     * ```
1762
     * $email->setAttachments(['custom_name.txt' => 'path/to/file.txt']);
1763
     * ```
1764
     *
1765
     * Attach a file and specify additional properties:
1766
     *
1767
     * ```
1768
     * $email->setAttachments(['custom_name.png' => [
1769
     *      'file' => 'path/to/file',
1770
     *      'mimetype' => 'image/png',
1771
     *      'contentId' => 'abc123',
1772
     *      'contentDisposition' => false
1773
     *    ]
1774
     * ]);
1775
     * ```
1776
     *
1777
     * Attach a file from string and specify additional properties:
1778
     *
1779
     * ```
1780
     * $email->setAttachments(['custom_name.png' => [
1781
     *      'data' => file_get_contents('path/to/file'),
1782
     *      'mimetype' => 'image/png'
1783
     *    ]
1784
     * ]);
1785
     * ```
1786
     *
1787
     * The `contentId` key allows you to specify an inline attachment. In your email text, you
1788
     * can use `<img src="cid:abc123" />` to display the image inline.
1789
     *
1790
     * The `contentDisposition` key allows you to disable the `Content-Disposition` header, this can improve
1791
     * attachment compatibility with outlook email clients.
1792
     *
1793
     * @param string|array $attachments String with the filename or array with filenames
1794
     * @return $this
1795
     * @throws \InvalidArgumentException
1796
     */
1797
    public function setAttachments($attachments)
1798
    {
1799
        $attach = [];
1800
        foreach ((array)$attachments as $name => $fileInfo) {
1801
            if (!is_array($fileInfo)) {
1802
                $fileInfo = ['file' => $fileInfo];
1803
            }
1804
            if (!isset($fileInfo['file'])) {
1805
                if (!isset($fileInfo['data'])) {
1806
                    throw new InvalidArgumentException('No file or data specified.');
1807
                }
1808
                if (is_int($name)) {
1809
                    throw new InvalidArgumentException('No filename specified.');
1810
                }
1811
                $fileInfo['data'] = chunk_split(base64_encode($fileInfo['data']), 76, "\r\n");
1812
            } else {
1813
                $fileName = $fileInfo['file'];
1814
                $fileInfo['file'] = realpath($fileInfo['file']);
1815
                if ($fileInfo['file'] === false || !file_exists($fileInfo['file'])) {
1816
                    throw new InvalidArgumentException(sprintf('File not found: "%s"', $fileName));
1817
                }
1818
                if (is_int($name)) {
1819
                    $name = basename($fileInfo['file']);
1820
                }
1821
            }
1822 View Code Duplication
            if (!isset($fileInfo['mimetype']) && isset($fileInfo['file']) && function_exists('mime_content_type')) {
1823
                $fileInfo['mimetype'] = mime_content_type($fileInfo['file']);
1824
            }
1825
            if (!isset($fileInfo['mimetype'])) {
1826
                $fileInfo['mimetype'] = 'application/octet-stream';
1827
            }
1828
            $attach[$name] = $fileInfo;
1829
        }
1830
        $this->_attachments = $attach;
1831
1832
        return $this;
1833
    }
1834
1835
    /**
1836
     * Gets attachments to the email message.
1837
     *
1838
     * @return array Array of attachments.
1839
     */
1840
    public function getAttachments()
1841
    {
1842
        return $this->_attachments;
1843
    }
1844
1845
    /**
1846
     * Add attachments to the email message
1847
     *
1848
     * Attachments can be defined in a few forms depending on how much control you need:
1849
     *
1850
     * Attach a single file:
1851
     *
1852
     * ```
1853
     * $email->setAttachments('path/to/file');
1854
     * ```
1855
     *
1856
     * Attach a file with a different filename:
1857
     *
1858
     * ```
1859
     * $email->setAttachments(['custom_name.txt' => 'path/to/file.txt']);
1860
     * ```
1861
     *
1862
     * Attach a file and specify additional properties:
1863
     *
1864
     * ```
1865
     * $email->setAttachments(['custom_name.png' => [
1866
     *      'file' => 'path/to/file',
1867
     *      'mimetype' => 'image/png',
1868
     *      'contentId' => 'abc123',
1869
     *      'contentDisposition' => false
1870
     *    ]
1871
     * ]);
1872
     * ```
1873
     *
1874
     * Attach a file from string and specify additional properties:
1875
     *
1876
     * ```
1877
     * $email->setAttachments(['custom_name.png' => [
1878
     *      'data' => file_get_contents('path/to/file'),
1879
     *      'mimetype' => 'image/png'
1880
     *    ]
1881
     * ]);
1882
     * ```
1883
     *
1884
     * The `contentId` key allows you to specify an inline attachment. In your email text, you
1885
     * can use `<img src="cid:abc123" />` to display the image inline.
1886
     *
1887
     * The `contentDisposition` key allows you to disable the `Content-Disposition` header, this can improve
1888
     * attachment compatibility with outlook email clients.
1889
     *
1890
     * @deprecated 3.4.0 Use setAttachments()/getAttachments() instead.
1891
     * @param string|array|null $attachments String with the filename or array with filenames
1892
     * @return array|$this Either the array of attachments when getting or $this when setting.
1893
     * @throws \InvalidArgumentException
1894
     */
1895
    public function attachments($attachments = null)
1896
    {
1897
        deprecationWarning('Email::attachments() is deprecated. Use Email::setAttachments() or Email::getAttachments() instead.');
1898
1899
        if ($attachments === null) {
1900
            return $this->getAttachments();
1901
        }
1902
1903
        return $this->setAttachments($attachments);
1904
    }
1905
1906
    /**
1907
     * Add attachments
1908
     *
1909
     * @param string|array $attachments String with the filename or array with filenames
1910
     * @return $this
1911
     * @throws \InvalidArgumentException
1912
     * @see \Cake\Mailer\Email::attachments()
1913
     */
1914
    public function addAttachments($attachments)
1915
    {
1916
        $current = $this->_attachments;
1917
        $this->setAttachments($attachments);
1918
        $this->_attachments = array_merge($current, $this->_attachments);
1919
1920
        return $this;
1921
    }
1922
1923
    /**
1924
     * Get generated message (used by transport classes)
1925
     *
1926
     * @param string|null $type Use MESSAGE_* constants or null to return the full message as array
1927
     * @return string|array String if type is given, array if type is null
1928
     */
1929
    public function message($type = null)
1930
    {
1931
        switch ($type) {
1932
            case static::MESSAGE_HTML:
1933
                return $this->_htmlMessage;
1934
            case static::MESSAGE_TEXT:
1935
                return $this->_textMessage;
1936
        }
1937
1938
        return $this->_message;
1939
    }
1940
1941
    /**
1942
     * Sets priority.
1943
     *
1944
     * @param int|null $priority 1 (highest) to 5 (lowest)
1945
     * @return $this
1946
     */
1947
    public function setPriority($priority)
1948
    {
1949
        $this->_priority = $priority;
1950
1951
        return $this;
1952
    }
1953
1954
    /**
1955
     * Gets priority.
1956
     *
1957
     * @return int
1958
     */
1959
    public function getPriority()
1960
    {
1961
        return $this->_priority;
1962
    }
1963
1964
    /**
1965
     * Sets transport configuration.
1966
     *
1967
     * Use this method to define transports to use in delivery profiles.
1968
     * Once defined you cannot edit the configurations, and must use
1969
     * Email::dropTransport() to flush the configuration first.
1970
     *
1971
     * When using an array of configuration data a new transport
1972
     * will be constructed for each message sent. When using a Closure, the
1973
     * closure will be evaluated for each message.
1974
     *
1975
     * The `className` is used to define the class to use for a transport.
1976
     * It can either be a short name, or a fully qualified class name
1977
     *
1978
     * @param string|array $key The configuration name to write. Or
1979
     *   an array of multiple transports to set.
1980
     * @param array|\Cake\Mailer\AbstractTransport|null $config Either an array of configuration
1981
     *   data, or a transport instance. Null when using key as array.
1982
     * @return void
1983
     * @deprecated 3.7.0 Use TransportFactory::setConfig() instead.
1984
     */
1985
    public static function setConfigTransport($key, $config = null)
1986
    {
1987
        deprecationWarning('Email::setConfigTransport() is deprecated. Use TransportFactory::setConfig() instead.');
1988
1989
        TransportFactory::setConfig($key, $config);
1990
    }
1991
1992
    /**
1993
     * Gets current transport configuration.
1994
     *
1995
     * @param string $key The configuration name to read.
1996
     * @return array|null Transport config.
1997
     * @deprecated 3.7.0 Use TransportFactory::getConfig() instead.
1998
     */
1999
    public static function getConfigTransport($key)
2000
    {
2001
        deprecationWarning('Email::getConfigTransport() is deprecated. Use TransportFactory::getConfig() instead.');
2002
2003
        return TransportFactory::getConfig($key);
2004
    }
2005
2006
    /**
2007
     * Add or read transport configuration.
2008
     *
2009
     * Use this method to define transports to use in delivery profiles.
2010
     * Once defined you cannot edit the configurations, and must use
2011
     * Email::dropTransport() to flush the configuration first.
2012
     *
2013
     * When using an array of configuration data a new transport
2014
     * will be constructed for each message sent. When using a Closure, the
2015
     * closure will be evaluated for each message.
2016
     *
2017
     * The `className` is used to define the class to use for a transport.
2018
     * It can either be a short name, or a fully qualified classname
2019
     *
2020
     * @deprecated 3.4.0 Use TransportFactory::setConfig()/getConfig() instead.
2021
     * @param string|array $key The configuration name to read/write. Or
2022
     *   an array of multiple transports to set.
2023
     * @param array|\Cake\Mailer\AbstractTransport|null $config Either an array of configuration
2024
     *   data, or a transport instance.
2025
     * @return array|null Either null when setting or an array of data when reading.
2026
     * @throws \BadMethodCallException When modifying an existing configuration.
2027
     */
2028
    public static function configTransport($key, $config = null)
2029
    {
2030
        deprecationWarning('Email::configTransport() is deprecated. Use TransportFactory::setConfig() or TransportFactory::getConfig() instead.');
2031
2032
        if ($config === null && is_string($key)) {
2033
            return TransportFactory::getConfig($key);
2034
        }
2035
        if ($config === null && is_array($key)) {
2036
            TransportFactory::setConfig($key);
2037
2038
            return null;
2039
        }
2040
2041
        TransportFactory::setConfig($key, $config);
2042
    }
2043
2044
    /**
2045
     * Returns an array containing the named transport configurations
2046
     *
2047
     * @return array Array of configurations.
2048
     * @deprecated 3.7.0 Use TransportFactory::configured() instead.
2049
     */
2050
    public static function configuredTransport()
2051
    {
2052
        deprecationWarning('Email::configuredTransport() is deprecated. Use TransportFactory::configured().');
2053
2054
        return TransportFactory::configured();
2055
    }
2056
2057
    /**
2058
     * Delete transport configuration.
2059
     *
2060
     * @param string $key The transport name to remove.
2061
     * @return void
2062
     * @deprecated 3.7.0 Use TransportFactory::drop() instead.
2063
     */
2064
    public static function dropTransport($key)
2065
    {
2066
        deprecationWarning('Email::dropTransport() is deprecated. Use TransportFactory::drop().');
2067
2068
        TransportFactory::drop($key);
2069
    }
2070
2071
    /**
2072
     * Sets the configuration profile to use for this instance.
2073
     *
2074
     * @param string|array $config String with configuration name, or
2075
     *    an array with config.
2076
     * @return $this
2077
     */
2078
    public function setProfile($config)
2079
    {
2080
        if (!is_array($config)) {
2081
            $config = (string)$config;
2082
        }
2083
        $this->_applyConfig($config);
2084
2085
        return $this;
2086
    }
2087
2088
    /**
2089
     * Gets the configuration profile to use for this instance.
2090
     *
2091
     * @return string|array
2092
     */
2093
    public function getProfile()
2094
    {
2095
        return $this->_profile;
2096
    }
2097
2098
    /**
2099
     * Get/Set the configuration profile to use for this instance.
2100
     *
2101
     * @deprecated 3.4.0 Use setProfile()/getProfile() instead.
2102
     * @param array|string|null $config String with configuration name, or
2103
     *    an array with config or null to return current config.
2104
     * @return string|array|$this
2105
     */
2106
    public function profile($config = null)
2107
    {
2108
        deprecationWarning('Email::profile() is deprecated. Use Email::setProfile() or Email::getProfile() instead.');
2109
2110
        if ($config === null) {
2111
            return $this->getProfile();
2112
        }
2113
2114
        return $this->setProfile($config);
2115
    }
2116
2117
    /**
2118
     * Send an email using the specified content, template and layout
2119
     *
2120
     * @param string|array|null $content String with message or array with messages
2121
     * @return array
2122
     * @throws \BadMethodCallException
2123
     */
2124
    public function send($content = null)
2125
    {
2126
        if (empty($this->_from)) {
2127
            throw new BadMethodCallException('From is not specified.');
2128
        }
2129
        if (empty($this->_to) && empty($this->_cc) && empty($this->_bcc)) {
2130
            throw new BadMethodCallException('You need specify one destination on to, cc or bcc.');
2131
        }
2132
2133
        if (is_array($content)) {
2134
            $content = implode("\n", $content) . "\n";
2135
        }
2136
2137
        $this->_message = $this->_render($this->_wrap($content));
2138
2139
        $transport = $this->getTransport();
2140
        if (!$transport) {
2141
            $msg = 'Cannot send email, transport was not defined. Did you call transport() or define ' .
2142
                ' a transport in the set profile?';
2143
            throw new BadMethodCallException($msg);
2144
        }
2145
        $contents = $transport->send($this);
2146
        $this->_logDelivery($contents);
2147
2148
        return $contents;
2149
    }
2150
2151
    /**
2152
     * Log the email message delivery.
2153
     *
2154
     * @param array $contents The content with 'headers' and 'message' keys.
2155
     * @return void
2156
     */
2157
    protected function _logDelivery($contents)
2158
    {
2159
        if (empty($this->_profile['log'])) {
2160
            return;
2161
        }
2162
        $config = [
2163
            'level' => 'debug',
2164
            'scope' => 'email',
2165
        ];
2166
        if ($this->_profile['log'] !== true) {
2167
            if (!is_array($this->_profile['log'])) {
2168
                $this->_profile['log'] = ['level' => $this->_profile['log']];
2169
            }
2170
            $config = $this->_profile['log'] + $config;
2171
        }
2172
        Log::write(
2173
            $config['level'],
2174
            PHP_EOL . $this->flatten($contents['headers']) . PHP_EOL . PHP_EOL . $this->flatten($contents['message']),
2175
            $config['scope']
2176
        );
2177
    }
2178
2179
    /**
2180
     * Converts given value to string
2181
     *
2182
     * @param string|array $value The value to convert
2183
     * @return string
2184
     */
2185
    protected function flatten($value)
2186
    {
2187
        return is_array($value) ? implode(';', $value) : (string)$value;
2188
    }
2189
2190
    /**
2191
     * Static method to fast create an instance of \Cake\Mailer\Email
2192
     *
2193
     * @param string|array|null $to Address to send (see Cake\Mailer\Email::to()). If null, will try to use 'to' from transport config
2194
     * @param string|null $subject String of subject or null to use 'subject' from transport config
2195
     * @param string|array|null $message String with message or array with variables to be used in render
2196
     * @param string|array $config String to use Email delivery profile from app.php or array with configs
2197
     * @param bool $send Send the email or just return the instance pre-configured
2198
     * @return static Instance of Cake\Mailer\Email
2199
     * @throws \InvalidArgumentException
2200
     */
2201
    public static function deliver($to = null, $subject = null, $message = null, $config = 'default', $send = true)
2202
    {
2203
        $class = __CLASS__;
2204
2205
        if (is_array($config) && !isset($config['transport'])) {
2206
            $config['transport'] = 'default';
2207
        }
2208
        /** @var \Cake\Mailer\Email $instance */
2209
        $instance = new $class($config);
2210
        if ($to !== null) {
2211
            $instance->setTo($to);
2212
        }
2213
        if ($subject !== null) {
2214
            $instance->setSubject($subject);
2215
        }
2216
        if (is_array($message)) {
2217
            $instance->setViewVars($message);
2218
            $message = null;
2219
        } elseif ($message === null && array_key_exists('message', $config = $instance->getProfile())) {
2220
            $message = $config['message'];
2221
        }
2222
2223
        if ($send === true) {
2224
            $instance->send($message);
2225
        }
2226
2227
        return $instance;
2228
    }
2229
2230
    /**
2231
     * Apply the config to an instance
2232
     *
2233
     * @param string|array $config Configuration options.
2234
     * @return void
2235
     * @throws \InvalidArgumentException When using a configuration that doesn't exist.
2236
     */
2237
    protected function _applyConfig($config)
2238
    {
2239
        if (is_string($config)) {
2240
            $name = $config;
2241
            $config = static::getConfig($name);
2242
            if (empty($config)) {
2243
                throw new InvalidArgumentException(sprintf('Unknown email configuration "%s".', $name));
2244
            }
2245
            unset($name);
2246
        }
2247
2248
        $this->_profile = array_merge($this->_profile, $config);
2249
2250
        $simpleMethods = [
2251
            'from', 'sender', 'to', 'replyTo', 'readReceipt', 'returnPath',
2252
            'cc', 'bcc', 'messageId', 'domain', 'subject', 'attachments',
2253
            'transport', 'emailFormat', 'emailPattern', 'charset', 'headerCharset',
2254
        ];
2255
        foreach ($simpleMethods as $method) {
2256
            if (isset($config[$method])) {
2257
                $this->{'set' . ucfirst($method)}($config[$method]);
2258
            }
2259
        }
2260
2261
        if (empty($this->headerCharset)) {
2262
            $this->headerCharset = $this->charset;
2263
        }
2264
        if (isset($config['headers'])) {
2265
            $this->setHeaders($config['headers']);
2266
        }
2267
2268
        $viewBuilderMethods = [
2269
            'template', 'layout', 'theme',
2270
        ];
2271
        foreach ($viewBuilderMethods as $method) {
2272
            if (array_key_exists($method, $config)) {
2273
                $this->viewBuilder()->{'set' . ucfirst($method)}($config[$method]);
2274
            }
2275
        }
2276
2277
        if (array_key_exists('helpers', $config)) {
2278
            $this->viewBuilder()->setHelpers($config['helpers'], false);
2279
        }
2280
        if (array_key_exists('viewRender', $config)) {
2281
            $this->viewBuilder()->setClassName($config['viewRender']);
2282
        }
2283
        if (array_key_exists('viewVars', $config)) {
2284
            $this->set($config['viewVars']);
2285
        }
2286
    }
2287
2288
    /**
2289
     * Reset all the internal variables to be able to send out a new email.
2290
     *
2291
     * @return $this
2292
     */
2293
    public function reset()
2294
    {
2295
        $this->_to = [];
2296
        $this->_from = [];
2297
        $this->_sender = [];
2298
        $this->_replyTo = [];
2299
        $this->_readReceipt = [];
2300
        $this->_returnPath = [];
2301
        $this->_cc = [];
2302
        $this->_bcc = [];
2303
        $this->_messageId = true;
2304
        $this->_subject = '';
2305
        $this->_headers = [];
2306
        $this->_textMessage = '';
2307
        $this->_htmlMessage = '';
2308
        $this->_message = [];
2309
        $this->_emailFormat = 'text';
2310
        $this->_transport = null;
2311
        $this->_priority = null;
2312
        $this->charset = 'utf-8';
2313
        $this->headerCharset = null;
2314
        $this->transferEncoding = null;
2315
        $this->_attachments = [];
2316
        $this->_profile = [];
2317
        $this->_emailPattern = self::EMAIL_PATTERN;
2318
2319
        $this->viewBuilder()->setLayout('default');
2320
        $this->viewBuilder()->setTemplate('');
2321
        $this->viewBuilder()->setClassName('Cake\View\View');
2322
        $this->viewVars = [];
2323
        $this->viewBuilder()->setTheme(false);
2324
        $this->viewBuilder()->setHelpers(['Html'], false);
2325
2326
        return $this;
2327
    }
2328
2329
    /**
2330
     * Encode the specified string using the current charset
2331
     *
2332
     * @param string $text String to encode
2333
     * @return string Encoded string
2334
     */
2335
    protected function _encode($text)
2336
    {
2337
        $restore = mb_internal_encoding();
2338
        mb_internal_encoding($this->_appCharset);
2339
        if (empty($this->headerCharset)) {
2340
            $this->headerCharset = $this->charset;
2341
        }
2342
        $return = mb_encode_mimeheader($text, $this->headerCharset, 'B');
2343
        mb_internal_encoding($restore);
2344
2345
        return $return;
2346
    }
2347
2348
    /**
2349
     * Decode the specified string
2350
     *
2351
     * @param string $text String to decode
2352
     * @return string Decoded string
2353
     */
2354
    protected function _decode($text)
2355
    {
2356
        $restore = mb_internal_encoding();
2357
        mb_internal_encoding($this->_appCharset);
2358
        $return = mb_decode_mimeheader($text);
2359
        mb_internal_encoding($restore);
2360
2361
        return $return;
2362
    }
2363
2364
    /**
2365
     * Translates a string for one charset to another if the App.encoding value
2366
     * differs and the mb_convert_encoding function exists
2367
     *
2368
     * @param string $text The text to be converted
2369
     * @param string $charset the target encoding
2370
     * @return string
2371
     */
2372
    protected function _encodeString($text, $charset)
2373
    {
2374
        if ($this->_appCharset === $charset) {
2375
            return $text;
2376
        }
2377
2378
        return mb_convert_encoding($text, $charset, $this->_appCharset);
2379
    }
2380
2381
    /**
2382
     * Wrap the message to follow the RFC 2822 - 2.1.1
2383
     *
2384
     * @param string $message Message to wrap
2385
     * @param int $wrapLength The line length
2386
     * @return array Wrapped message
2387
     */
2388
    protected function _wrap($message, $wrapLength = Email::LINE_LENGTH_MUST)
2389
    {
2390
        if (strlen($message) === 0) {
2391
            return [''];
2392
        }
2393
        $message = str_replace(["\r\n", "\r"], "\n", $message);
2394
        $lines = explode("\n", $message);
2395
        $formatted = [];
2396
        $cut = ($wrapLength == Email::LINE_LENGTH_MUST);
2397
2398
        foreach ($lines as $line) {
2399
            if (empty($line) && $line !== '0') {
2400
                $formatted[] = '';
2401
                continue;
2402
            }
2403
            if (strlen($line) < $wrapLength) {
2404
                $formatted[] = $line;
2405
                continue;
2406
            }
2407
            if (!preg_match('/<[a-z]+.*>/i', $line)) {
2408
                $formatted = array_merge(
2409
                    $formatted,
2410
                    explode("\n", Text::wordWrap($line, $wrapLength, "\n", $cut))
2411
                );
2412
                continue;
2413
            }
2414
2415
            $tagOpen = false;
2416
            $tmpLine = $tag = '';
2417
            $tmpLineLength = 0;
2418
            for ($i = 0, $count = strlen($line); $i < $count; $i++) {
2419
                $char = $line[$i];
2420
                if ($tagOpen) {
2421
                    $tag .= $char;
2422
                    if ($char === '>') {
2423
                        $tagLength = strlen($tag);
2424
                        if ($tagLength + $tmpLineLength < $wrapLength) {
2425
                            $tmpLine .= $tag;
2426
                            $tmpLineLength += $tagLength;
2427
                        } else {
2428
                            if ($tmpLineLength > 0) {
2429
                                $formatted = array_merge(
2430
                                    $formatted,
2431
                                    explode("\n", Text::wordWrap(trim($tmpLine), $wrapLength, "\n", $cut))
2432
                                );
2433
                                $tmpLine = '';
2434
                                $tmpLineLength = 0;
2435
                            }
2436
                            if ($tagLength > $wrapLength) {
2437
                                $formatted[] = $tag;
2438
                            } else {
2439
                                $tmpLine = $tag;
2440
                                $tmpLineLength = $tagLength;
2441
                            }
2442
                        }
2443
                        $tag = '';
2444
                        $tagOpen = false;
2445
                    }
2446
                    continue;
2447
                }
2448
                if ($char === '<') {
2449
                    $tagOpen = true;
2450
                    $tag = '<';
2451
                    continue;
2452
                }
2453
                if ($char === ' ' && $tmpLineLength >= $wrapLength) {
2454
                    $formatted[] = $tmpLine;
2455
                    $tmpLineLength = 0;
2456
                    continue;
2457
                }
2458
                $tmpLine .= $char;
2459
                $tmpLineLength++;
2460
                if ($tmpLineLength === $wrapLength) {
2461
                    $nextChar = $line[$i + 1];
2462
                    if ($nextChar === ' ' || $nextChar === '<') {
2463
                        $formatted[] = trim($tmpLine);
2464
                        $tmpLine = '';
2465
                        $tmpLineLength = 0;
2466
                        if ($nextChar === ' ') {
2467
                            $i++;
2468
                        }
2469 View Code Duplication
                    } else {
2470
                        $lastSpace = strrpos($tmpLine, ' ');
2471
                        if ($lastSpace === false) {
2472
                            continue;
2473
                        }
2474
                        $formatted[] = trim(substr($tmpLine, 0, $lastSpace));
2475
                        $tmpLine = substr($tmpLine, $lastSpace + 1);
2476
2477
                        $tmpLineLength = strlen($tmpLine);
2478
                    }
2479
                }
2480
            }
2481
            if (!empty($tmpLine)) {
2482
                $formatted[] = $tmpLine;
2483
            }
2484
        }
2485
        $formatted[] = '';
2486
2487
        return $formatted;
2488
    }
2489
2490
    /**
2491
     * Create unique boundary identifier
2492
     *
2493
     * @return void
2494
     */
2495
    protected function _createBoundary()
2496
    {
2497
        if ($this->_attachments || $this->_emailFormat === 'both') {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_attachments of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2498
            $this->_boundary = md5(Security::randomBytes(16));
2499
        }
2500
    }
2501
2502
    /**
2503
     * Attach non-embedded files by adding file contents inside boundaries.
2504
     *
2505
     * @param string|null $boundary Boundary to use. If null, will default to $this->_boundary
2506
     * @return array An array of lines to add to the message
2507
     */
2508
    protected function _attachFiles($boundary = null)
2509
    {
2510
        if ($boundary === null) {
2511
            $boundary = $this->_boundary;
2512
        }
2513
2514
        $msg = [];
2515
        foreach ($this->_attachments as $filename => $fileInfo) {
2516
            if (!empty($fileInfo['contentId'])) {
2517
                continue;
2518
            }
2519
            $data = isset($fileInfo['data']) ? $fileInfo['data'] : $this->_readFile($fileInfo['file']);
2520
            $hasDisposition = (
2521
                !isset($fileInfo['contentDisposition']) ||
2522
                $fileInfo['contentDisposition']
2523
            );
2524
            $part = new FormDataPart('', $data, '', $this->getHeaderCharset());
2525
2526
            if ($hasDisposition) {
2527
                $part->disposition('attachment');
2528
                $part->filename($filename);
2529
            }
2530
            $part->transferEncoding('base64');
2531
            $part->type($fileInfo['mimetype']);
2532
2533
            $msg[] = '--' . $boundary;
2534
            $msg[] = (string)$part;
2535
            $msg[] = '';
2536
        }
2537
2538
        return $msg;
2539
    }
2540
2541
    /**
2542
     * Read the file contents and return a base64 version of the file contents.
2543
     *
2544
     * @param string $path The absolute path to the file to read.
2545
     * @return string File contents in base64 encoding
2546
     */
2547
    protected function _readFile($path)
2548
    {
2549
        $File = new File($path);
2550
2551
        return chunk_split(base64_encode($File->read()));
2552
    }
2553
2554
    /**
2555
     * Attach inline/embedded files to the message.
2556
     *
2557
     * @param string|null $boundary Boundary to use. If null, will default to $this->_boundary
2558
     * @return array An array of lines to add to the message
2559
     */
2560
    protected function _attachInlineFiles($boundary = null)
2561
    {
2562
        if ($boundary === null) {
2563
            $boundary = $this->_boundary;
2564
        }
2565
2566
        $msg = [];
2567
        foreach ($this->_attachments as $filename => $fileInfo) {
2568
            if (empty($fileInfo['contentId'])) {
2569
                continue;
2570
            }
2571
            $data = isset($fileInfo['data']) ? $fileInfo['data'] : $this->_readFile($fileInfo['file']);
2572
2573
            $msg[] = '--' . $boundary;
2574
            $part = new FormDataPart('', $data, 'inline', $this->getHeaderCharset());
2575
            $part->type($fileInfo['mimetype']);
2576
            $part->transferEncoding('base64');
2577
            $part->contentId($fileInfo['contentId']);
2578
            $part->filename($filename);
2579
            $msg[] = (string)$part;
2580
            $msg[] = '';
2581
        }
2582
2583
        return $msg;
2584
    }
2585
2586
    /**
2587
     * Render the body of the email.
2588
     *
2589
     * @param array $content Content to render
2590
     * @return array Email body ready to be sent
2591
     */
2592
    protected function _render($content)
2593
    {
2594
        $this->_textMessage = $this->_htmlMessage = '';
2595
2596
        $content = implode("\n", $content);
2597
        $rendered = $this->_renderTemplates($content);
2598
2599
        $this->_createBoundary();
2600
        $msg = [];
2601
2602
        $contentIds = array_filter((array)Hash::extract($this->_attachments, '{s}.contentId'));
2603
        $hasInlineAttachments = count($contentIds) > 0;
2604
        $hasAttachments = !empty($this->_attachments);
2605
        $hasMultipleTypes = count($rendered) > 1;
2606
        $multiPart = ($hasAttachments || $hasMultipleTypes);
2607
2608
        $boundary = $relBoundary = $textBoundary = $this->_boundary;
2609
2610 View Code Duplication
        if ($hasInlineAttachments) {
2611
            $msg[] = '--' . $boundary;
2612
            $msg[] = 'Content-Type: multipart/related; boundary="rel-' . $boundary . '"';
2613
            $msg[] = '';
2614
            $relBoundary = $textBoundary = 'rel-' . $boundary;
2615
        }
2616
2617 View Code Duplication
        if ($hasMultipleTypes && $hasAttachments) {
2618
            $msg[] = '--' . $relBoundary;
2619
            $msg[] = 'Content-Type: multipart/alternative; boundary="alt-' . $boundary . '"';
2620
            $msg[] = '';
2621
            $textBoundary = 'alt-' . $boundary;
2622
        }
2623
2624 View Code Duplication
        if (isset($rendered['text'])) {
2625
            if ($multiPart) {
2626
                $msg[] = '--' . $textBoundary;
2627
                $msg[] = 'Content-Type: text/plain; charset=' . $this->_getContentTypeCharset();
2628
                $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding();
2629
                $msg[] = '';
2630
            }
2631
            $this->_textMessage = $rendered['text'];
2632
            $content = explode("\n", $this->_textMessage);
2633
            $msg = array_merge($msg, $content);
2634
            $msg[] = '';
2635
        }
2636
2637 View Code Duplication
        if (isset($rendered['html'])) {
2638
            if ($multiPart) {
2639
                $msg[] = '--' . $textBoundary;
2640
                $msg[] = 'Content-Type: text/html; charset=' . $this->_getContentTypeCharset();
2641
                $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding();
2642
                $msg[] = '';
2643
            }
2644
            $this->_htmlMessage = $rendered['html'];
2645
            $content = explode("\n", $this->_htmlMessage);
2646
            $msg = array_merge($msg, $content);
2647
            $msg[] = '';
2648
        }
2649
2650
        if ($textBoundary !== $relBoundary) {
2651
            $msg[] = '--' . $textBoundary . '--';
2652
            $msg[] = '';
2653
        }
2654
2655
        if ($hasInlineAttachments) {
2656
            $attachments = $this->_attachInlineFiles($relBoundary);
2657
            $msg = array_merge($msg, $attachments);
2658
            $msg[] = '';
2659
            $msg[] = '--' . $relBoundary . '--';
2660
            $msg[] = '';
2661
        }
2662
2663
        if ($hasAttachments) {
2664
            $attachments = $this->_attachFiles($boundary);
2665
            $msg = array_merge($msg, $attachments);
2666
        }
2667
        if ($hasAttachments || $hasMultipleTypes) {
2668
            $msg[] = '';
2669
            $msg[] = '--' . $boundary . '--';
2670
            $msg[] = '';
2671
        }
2672
2673
        return $msg;
2674
    }
2675
2676
    /**
2677
     * Gets the text body types that are in this email message
2678
     *
2679
     * @return array Array of types. Valid types are 'text' and 'html'
2680
     */
2681
    protected function _getTypes()
2682
    {
2683
        $types = [$this->_emailFormat];
2684
        if ($this->_emailFormat === 'both') {
2685
            $types = ['html', 'text'];
2686
        }
2687
2688
        return $types;
2689
    }
2690
2691
    /**
2692
     * Build and set all the view properties needed to render the templated emails.
2693
     * If there is no template set, the $content will be returned in a hash
2694
     * of the text content types for the email.
2695
     *
2696
     * @param string $content The content passed in from send() in most cases.
2697
     * @return array The rendered content with html and text keys.
2698
     */
2699
    protected function _renderTemplates($content)
2700
    {
2701
        $types = $this->_getTypes();
2702
        $rendered = [];
2703
        $template = $this->viewBuilder()->getTemplate();
2704
        if (empty($template)) {
2705
            foreach ($types as $type) {
2706
                $rendered[$type] = $this->_encodeString($content, $this->charset);
2707
            }
2708
2709
            return $rendered;
2710
        }
2711
2712
        $View = $this->createView();
2713
2714
        list($templatePlugin) = pluginSplit($View->getTemplate());
2715
        list($layoutPlugin) = pluginSplit($View->getLayout());
2716
        if ($templatePlugin) {
2717
            $View->setPlugin($templatePlugin);
2718
        } elseif ($layoutPlugin) {
2719
            $View->setPlugin($layoutPlugin);
2720
        }
2721
2722
        if ($View->get('content') === null) {
2723
            $View->set('content', $content);
2724
        }
2725
2726
        foreach ($types as $type) {
2727
            $View->hasRendered = false;
0 ignored issues
show
Deprecated Code introduced by
The property Cake\View\View::$hasRendered has been deprecated with message: 3.7.0 The property is deprecated and will be removed in 4.0.0.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2728
            $View->setTemplatePath('Email' . DIRECTORY_SEPARATOR . $type);
2729
            $View->setLayoutPath('Email' . DIRECTORY_SEPARATOR . $type);
2730
2731
            $render = $View->render();
2732
            $render = str_replace(["\r\n", "\r"], "\n", $render);
2733
            $rendered[$type] = $this->_encodeString($render, $this->charset);
2734
        }
2735
2736
        foreach ($rendered as $type => $content) {
2737
            $rendered[$type] = $this->_wrap($content);
2738
            $rendered[$type] = implode("\n", $rendered[$type]);
2739
            $rendered[$type] = rtrim($rendered[$type], "\n");
2740
        }
2741
2742
        return $rendered;
2743
    }
2744
2745
    /**
2746
     * Return the Content-Transfer Encoding value based
2747
     * on the set transferEncoding or set charset.
2748
     *
2749
     * @return string
2750
     */
2751
    protected function _getContentTransferEncoding()
2752
    {
2753
        if ($this->transferEncoding) {
2754
            return $this->transferEncoding;
2755
        }
2756
2757
        $charset = strtoupper($this->charset);
2758
        if (in_array($charset, $this->_charset8bit)) {
2759
            return '8bit';
2760
        }
2761
2762
        return '7bit';
2763
    }
2764
2765
    /**
2766
     * Return charset value for Content-Type.
2767
     *
2768
     * Checks fallback/compatibility types which include workarounds
2769
     * for legacy japanese character sets.
2770
     *
2771
     * @return string
2772
     */
2773
    protected function _getContentTypeCharset()
2774
    {
2775
        $charset = strtoupper($this->charset);
2776
        if (array_key_exists($charset, $this->_contentTypeCharset)) {
2777
            return strtoupper($this->_contentTypeCharset[$charset]);
2778
        }
2779
2780
        return strtoupper($this->charset);
2781
    }
2782
2783
    /**
2784
     * Serializes the email object to a value that can be natively serialized and re-used
2785
     * to clone this email instance.
2786
     *
2787
     * It has certain limitations for viewVars that are good to know:
2788
     *
2789
     *    - ORM\Query executed and stored as resultset
2790
     *    - SimpleXMLElements stored as associative array
2791
     *    - Exceptions stored as strings
2792
     *    - Resources, \Closure and \PDO are not supported.
2793
     *
2794
     * @return array Serializable array of configuration properties.
2795
     * @throws \Exception When a view var object can not be properly serialized.
2796
     */
2797
    public function jsonSerialize()
2798
    {
2799
        $properties = [
2800
            '_to', '_from', '_sender', '_replyTo', '_cc', '_bcc', '_subject',
2801
            '_returnPath', '_readReceipt', '_emailFormat', '_emailPattern', '_domain',
2802
            '_attachments', '_messageId', '_headers', '_appCharset', 'viewVars', 'charset', 'headerCharset',
2803
        ];
2804
2805
        $array = ['viewConfig' => $this->viewBuilder()->jsonSerialize()];
2806
2807
        foreach ($properties as $property) {
2808
            $array[$property] = $this->{$property};
2809
        }
2810
2811
        array_walk($array['_attachments'], function (&$item, $key) {
2812
            if (!empty($item['file'])) {
2813
                $item['data'] = $this->_readFile($item['file']);
2814
                unset($item['file']);
2815
            }
2816
        });
2817
2818
        array_walk_recursive($array['viewVars'], [$this, '_checkViewVars']);
2819
2820
        return array_filter($array, function ($i) {
2821
            return !is_array($i) && strlen($i) || !empty($i);
2822
        });
2823
    }
2824
2825
    /**
2826
     * Iterates through hash to clean up and normalize.
2827
     *
2828
     * @param mixed $item Reference to the view var value.
2829
     * @param string $key View var key.
2830
     * @return void
2831
     */
2832
    protected function _checkViewVars(&$item, $key)
2833
    {
2834
        if ($item instanceof Exception) {
2835
            $item = (string)$item;
2836
        }
2837
2838
        if (
2839
            is_resource($item) ||
2840
            $item instanceof Closure ||
2841
            $item instanceof PDO
2842
        ) {
2843
            throw new RuntimeException(sprintf(
2844
                'Failed serializing the `%s` %s in the `%s` view var',
2845
                is_resource($item) ? get_resource_type($item) : get_class($item),
2846
                is_resource($item) ? 'resource' : 'object',
2847
                $key
2848
            ));
2849
        }
2850
    }
2851
2852
    /**
2853
     * Configures an email instance object from serialized config.
2854
     *
2855
     * @param array $config Email configuration array.
2856
     * @return $this Configured email instance.
2857
     */
2858
    public function createFromArray($config)
2859
    {
2860
        if (isset($config['viewConfig'])) {
2861
            $this->viewBuilder()->createFromArray($config['viewConfig']);
2862
            unset($config['viewConfig']);
2863
        }
2864
2865
        foreach ($config as $property => $value) {
2866
            $this->{$property} = $value;
2867
        }
2868
2869
        return $this;
2870
    }
2871
2872
    /**
2873
     * Serializes the Email object.
2874
     *
2875
     * @return string
2876
     */
2877
    public function serialize()
2878
    {
2879
        $array = $this->jsonSerialize();
2880
        array_walk_recursive($array, function (&$item, $key) {
2881
            if ($item instanceof SimpleXMLElement) {
2882
                $item = json_decode(json_encode((array)$item), true);
2883
            }
2884
        });
2885
2886
        return serialize($array);
2887
    }
2888
2889
    /**
2890
     * Unserializes the Email object.
2891
     *
2892
     * @param string $data Serialized string.
2893
     * @return static Configured email instance.
2894
     */
2895
    public function unserialize($data)
2896
    {
2897
        return $this->createFromArray(unserialize($data));
2898
    }
2899
}
2900