Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Email often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Email, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
50 | class Email implements JsonSerializable, Serializable |
||
51 | { |
||
52 | |||
53 | use StaticConfigTrait; |
||
54 | use ViewVarsTrait; |
||
55 | |||
56 | /** |
||
57 | * Line length - no should more - RFC 2822 - 2.1.1 |
||
58 | * |
||
59 | * @var int |
||
60 | */ |
||
61 | const LINE_LENGTH_SHOULD = 78; |
||
62 | |||
63 | /** |
||
64 | * Line length - no must more - RFC 2822 - 2.1.1 |
||
65 | * |
||
66 | * @var int |
||
67 | */ |
||
68 | const LINE_LENGTH_MUST = 998; |
||
69 | |||
70 | /** |
||
71 | * Type of message - HTML |
||
72 | * |
||
73 | * @var string |
||
74 | */ |
||
75 | const MESSAGE_HTML = 'html'; |
||
76 | |||
77 | /** |
||
78 | * Type of message - TEXT |
||
79 | * |
||
80 | * @var string |
||
81 | */ |
||
82 | const MESSAGE_TEXT = 'text'; |
||
83 | |||
84 | /** |
||
85 | * Holds the regex pattern for email validation |
||
86 | * |
||
87 | * @var string |
||
88 | */ |
||
89 | const EMAIL_PATTERN = '/^((?:[\p{L}0-9.!#$%&\'*+\/=?^_`{|}~-]+)*@[\p{L}0-9-.]+)$/ui'; |
||
90 | |||
91 | /** |
||
92 | * Recipient of the email |
||
93 | * |
||
94 | * @var array |
||
95 | */ |
||
96 | protected $_to = []; |
||
97 | |||
98 | /** |
||
99 | * The mail which the email is sent from |
||
100 | * |
||
101 | * @var array |
||
102 | */ |
||
103 | protected $_from = []; |
||
104 | |||
105 | /** |
||
106 | * The sender email |
||
107 | * |
||
108 | * @var array |
||
109 | */ |
||
110 | protected $_sender = []; |
||
111 | |||
112 | /** |
||
113 | * The email the recipient will reply to |
||
114 | * |
||
115 | * @var array |
||
116 | */ |
||
117 | protected $_replyTo = []; |
||
118 | |||
119 | /** |
||
120 | * The read receipt email |
||
121 | * |
||
122 | * @var array |
||
123 | */ |
||
124 | protected $_readReceipt = []; |
||
125 | |||
126 | /** |
||
127 | * The mail that will be used in case of any errors like |
||
128 | * - Remote mailserver down |
||
129 | * - Remote user has exceeded his quota |
||
130 | * - Unknown user |
||
131 | * |
||
132 | * @var array |
||
133 | */ |
||
134 | protected $_returnPath = []; |
||
135 | |||
136 | /** |
||
137 | * Carbon Copy |
||
138 | * |
||
139 | * List of email's that should receive a copy of the email. |
||
140 | * The Recipient WILL be able to see this list |
||
141 | * |
||
142 | * @var array |
||
143 | */ |
||
144 | protected $_cc = []; |
||
145 | |||
146 | /** |
||
147 | * Blind Carbon Copy |
||
148 | * |
||
149 | * List of email's that should receive a copy of the email. |
||
150 | * The Recipient WILL NOT be able to see this list |
||
151 | * |
||
152 | * @var array |
||
153 | */ |
||
154 | protected $_bcc = []; |
||
155 | |||
156 | /** |
||
157 | * Message ID |
||
158 | * |
||
159 | * @var bool|string |
||
160 | */ |
||
161 | protected $_messageId = true; |
||
162 | |||
163 | /** |
||
164 | * Domain for messageId generation. |
||
165 | * Needs to be manually set for CLI mailing as env('HTTP_HOST') is empty |
||
166 | * |
||
167 | * @var string |
||
168 | */ |
||
169 | protected $_domain = null; |
||
170 | |||
171 | /** |
||
172 | * The subject of the email |
||
173 | * |
||
174 | * @var string |
||
175 | */ |
||
176 | protected $_subject = ''; |
||
177 | |||
178 | /** |
||
179 | * Associative array of a user defined headers |
||
180 | * Keys will be prefixed 'X-' as per RFC2822 Section 4.7.5 |
||
181 | * |
||
182 | * @var array |
||
183 | */ |
||
184 | protected $_headers = []; |
||
185 | |||
186 | /** |
||
187 | * Text message |
||
188 | * |
||
189 | * @var string |
||
190 | */ |
||
191 | protected $_textMessage = ''; |
||
192 | |||
193 | /** |
||
194 | * Html message |
||
195 | * |
||
196 | * @var string |
||
197 | */ |
||
198 | protected $_htmlMessage = ''; |
||
199 | |||
200 | /** |
||
201 | * Final message to send |
||
202 | * |
||
203 | * @var array |
||
204 | */ |
||
205 | protected $_message = []; |
||
206 | |||
207 | /** |
||
208 | * Available formats to be sent. |
||
209 | * |
||
210 | * @var array |
||
211 | */ |
||
212 | protected $_emailFormatAvailable = ['text', 'html', 'both']; |
||
213 | |||
214 | /** |
||
215 | * What format should the email be sent in |
||
216 | * |
||
217 | * @var string |
||
218 | */ |
||
219 | protected $_emailFormat = 'text'; |
||
220 | |||
221 | /** |
||
222 | * The transport instance to use for sending mail. |
||
223 | * |
||
224 | * @var \Cake\Mailer\AbstractTransport |
||
225 | */ |
||
226 | protected $_transport = null; |
||
227 | |||
228 | /** |
||
229 | * Charset the email body is sent in |
||
230 | * |
||
231 | * @var string |
||
232 | */ |
||
233 | public $charset = 'utf-8'; |
||
234 | |||
235 | /** |
||
236 | * Charset the email header is sent in |
||
237 | * If null, the $charset property will be used as default |
||
238 | * |
||
239 | * @var string |
||
240 | */ |
||
241 | public $headerCharset = null; |
||
242 | |||
243 | /** |
||
244 | * The application wide charset, used to encode headers and body |
||
245 | * |
||
246 | * @var string |
||
247 | */ |
||
248 | protected $_appCharset = null; |
||
249 | |||
250 | /** |
||
251 | * List of files that should be attached to the email. |
||
252 | * |
||
253 | * Only absolute paths |
||
254 | * |
||
255 | * @var array |
||
256 | */ |
||
257 | protected $_attachments = []; |
||
258 | |||
259 | /** |
||
260 | * If set, boundary to use for multipart mime messages |
||
261 | * |
||
262 | * @var string |
||
263 | */ |
||
264 | protected $_boundary = null; |
||
265 | |||
266 | /** |
||
267 | * An array mapping url schemes to fully qualified Transport class names |
||
268 | * |
||
269 | * @var array |
||
270 | */ |
||
271 | protected static $_dsnClassMap = [ |
||
272 | 'debug' => 'Cake\Mailer\Transport\DebugTransport', |
||
273 | 'mail' => 'Cake\Mailer\Transport\MailTransport', |
||
274 | 'smtp' => 'Cake\Mailer\Transport\SmtpTransport', |
||
275 | ]; |
||
276 | |||
277 | /** |
||
278 | * Configuration profiles for transports. |
||
279 | * |
||
280 | * @var array |
||
281 | */ |
||
282 | protected static $_transportConfig = []; |
||
283 | |||
284 | /** |
||
285 | * A copy of the configuration profile for this |
||
286 | * instance. This copy can be modified with Email::profile(). |
||
287 | * |
||
288 | * @var array |
||
289 | */ |
||
290 | protected $_profile = []; |
||
291 | |||
292 | /** |
||
293 | * 8Bit character sets |
||
294 | * |
||
295 | * @var array |
||
296 | */ |
||
297 | protected $_charset8bit = ['UTF-8', 'SHIFT_JIS']; |
||
298 | |||
299 | /** |
||
300 | * Define Content-Type charset name |
||
301 | * |
||
302 | * @var array |
||
303 | */ |
||
304 | protected $_contentTypeCharset = [ |
||
305 | 'ISO-2022-JP-MS' => 'ISO-2022-JP' |
||
306 | ]; |
||
307 | |||
308 | /** |
||
309 | * Regex for email validation |
||
310 | * |
||
311 | * If null, filter_var() will be used. Use the emailPattern() method |
||
312 | * to set a custom pattern.' |
||
313 | * |
||
314 | * @var string |
||
315 | */ |
||
316 | protected $_emailPattern = self::EMAIL_PATTERN; |
||
317 | |||
318 | /** |
||
319 | * The class name used for email configuration. |
||
320 | * |
||
321 | * @var string |
||
322 | */ |
||
323 | protected $_configClass = 'EmailConfig'; |
||
324 | |||
325 | /** |
||
326 | * Constructor |
||
327 | * |
||
328 | * @param array|string|null $config Array of configs, or string to load configs from email.php |
||
329 | */ |
||
330 | public function __construct($config = null) |
||
331 | { |
||
332 | $this->_appCharset = Configure::read('App.encoding'); |
||
333 | if ($this->_appCharset !== null) { |
||
334 | $this->charset = $this->_appCharset; |
||
335 | } |
||
336 | $this->_domain = preg_replace('/\:\d+$/', '', env('HTTP_HOST')); |
||
337 | if (empty($this->_domain)) { |
||
338 | $this->_domain = php_uname('n'); |
||
339 | } |
||
340 | |||
341 | $this->viewBuilder() |
||
342 | ->className('Cake\View\View') |
||
343 | ->template('') |
||
344 | ->layout('default') |
||
345 | ->helpers(['Html']); |
||
346 | |||
347 | if ($config === null) { |
||
348 | $config = static::config('default'); |
||
349 | } |
||
350 | if ($config) { |
||
351 | $this->profile($config); |
||
352 | } |
||
353 | if (empty($this->headerCharset)) { |
||
354 | $this->headerCharset = $this->charset; |
||
355 | } |
||
356 | } |
||
357 | |||
358 | /** |
||
359 | * Clone ViewBuilder instance when email object is cloned. |
||
360 | * |
||
361 | * @return void |
||
362 | */ |
||
363 | public function __clone() |
||
367 | |||
368 | /** |
||
369 | * From |
||
370 | * |
||
371 | * @param string|array|null $email Null to get, String with email, |
||
372 | * Array with email as key, name as value or email as value (without name) |
||
373 | * @param string|null $name Name |
||
374 | * @return array|$this |
||
375 | * @throws \InvalidArgumentException |
||
376 | */ |
||
377 | public function from($email = null, $name = null) |
||
378 | { |
||
379 | if ($email === null) { |
||
380 | return $this->_from; |
||
381 | } |
||
382 | return $this->_setEmailSingle('_from', $email, $name, 'From requires only 1 email address.'); |
||
383 | } |
||
384 | |||
385 | /** |
||
386 | * Sender |
||
387 | * |
||
388 | * @param string|array|null $email Null to get, String with email, |
||
389 | * Array with email as key, name as value or email as value (without name) |
||
390 | * @param string|null $name Name |
||
391 | * @return array|$this |
||
392 | * @throws \InvalidArgumentException |
||
393 | */ |
||
394 | public function sender($email = null, $name = null) |
||
395 | { |
||
396 | if ($email === null) { |
||
397 | return $this->_sender; |
||
398 | } |
||
399 | return $this->_setEmailSingle('_sender', $email, $name, 'Sender requires only 1 email address.'); |
||
400 | } |
||
401 | |||
402 | /** |
||
403 | * Reply-To |
||
404 | * |
||
405 | * @param string|array|null $email Null to get, String with email, |
||
406 | * Array with email as key, name as value or email as value (without name) |
||
407 | * @param string|null $name Name |
||
408 | * @return array|$this |
||
409 | * @throws \InvalidArgumentException |
||
410 | */ |
||
411 | public function replyTo($email = null, $name = null) |
||
412 | { |
||
413 | if ($email === null) { |
||
414 | return $this->_replyTo; |
||
415 | } |
||
416 | return $this->_setEmailSingle('_replyTo', $email, $name, 'Reply-To requires only 1 email address.'); |
||
417 | } |
||
418 | |||
419 | /** |
||
420 | * Read Receipt (Disposition-Notification-To header) |
||
421 | * |
||
422 | * @param string|array|null $email Null to get, String with email, |
||
423 | * Array with email as key, name as value or email as value (without name) |
||
424 | * @param string|null $name Name |
||
425 | * @return array|$this |
||
426 | * @throws \InvalidArgumentException |
||
427 | */ |
||
428 | public function readReceipt($email = null, $name = null) |
||
429 | { |
||
430 | if ($email === null) { |
||
431 | return $this->_readReceipt; |
||
432 | } |
||
433 | return $this->_setEmailSingle('_readReceipt', $email, $name, 'Disposition-Notification-To requires only 1 email address.'); |
||
434 | } |
||
435 | |||
436 | /** |
||
437 | * Return Path |
||
438 | * |
||
439 | * @param string|array|null $email Null to get, String with email, |
||
440 | * Array with email as key, name as value or email as value (without name) |
||
441 | * @param string|null $name Name |
||
442 | * @return array|$this |
||
443 | * @throws \InvalidArgumentException |
||
444 | */ |
||
445 | public function returnPath($email = null, $name = null) |
||
446 | { |
||
447 | if ($email === null) { |
||
448 | return $this->_returnPath; |
||
449 | } |
||
450 | return $this->_setEmailSingle('_returnPath', $email, $name, 'Return-Path requires only 1 email address.'); |
||
451 | } |
||
452 | |||
453 | /** |
||
454 | * To |
||
455 | * |
||
456 | * @param string|array|null $email Null to get, String with email, |
||
457 | * Array with email as key, name as value or email as value (without name) |
||
458 | * @param string|null $name Name |
||
459 | * @return array|$this |
||
460 | */ |
||
461 | View Code Duplication | public function to($email = null, $name = null) |
|
462 | { |
||
463 | if ($email === null) { |
||
464 | return $this->_to; |
||
465 | } |
||
466 | return $this->_setEmail('_to', $email, $name); |
||
467 | } |
||
468 | |||
469 | /** |
||
470 | * Add To |
||
471 | * |
||
472 | * @param string|array $email Null to get, String with email, |
||
473 | * Array with email as key, name as value or email as value (without name) |
||
474 | * @param string|null $name Name |
||
475 | * @return $this |
||
476 | */ |
||
477 | public function addTo($email, $name = null) |
||
481 | |||
482 | /** |
||
483 | * Cc |
||
484 | * |
||
485 | * @param string|array|null $email Null to get, String with email, |
||
486 | * Array with email as key, name as value or email as value (without name) |
||
487 | * @param string|null $name Name |
||
488 | * @return array|$this |
||
489 | */ |
||
490 | View Code Duplication | public function cc($email = null, $name = null) |
|
491 | { |
||
492 | if ($email === null) { |
||
493 | return $this->_cc; |
||
494 | } |
||
495 | return $this->_setEmail('_cc', $email, $name); |
||
496 | } |
||
497 | |||
498 | /** |
||
499 | * Add Cc |
||
500 | * |
||
501 | * @param string|array $email Null to get, String with email, |
||
502 | * Array with email as key, name as value or email as value (without name) |
||
503 | * @param string|null $name Name |
||
504 | * @return $this |
||
505 | */ |
||
506 | public function addCc($email, $name = null) |
||
510 | |||
511 | /** |
||
512 | * Bcc |
||
513 | * |
||
514 | * @param string|array|null $email Null to get, String with email, |
||
515 | * Array with email as key, name as value or email as value (without name) |
||
516 | * @param string|null $name Name |
||
517 | * @return array|$this |
||
518 | */ |
||
519 | View Code Duplication | public function bcc($email = null, $name = null) |
|
520 | { |
||
521 | if ($email === null) { |
||
522 | return $this->_bcc; |
||
523 | } |
||
524 | return $this->_setEmail('_bcc', $email, $name); |
||
525 | } |
||
526 | |||
527 | /** |
||
528 | * Add Bcc |
||
529 | * |
||
530 | * @param string|array $email Null to get, String with email, |
||
531 | * Array with email as key, name as value or email as value (without name) |
||
532 | * @param string|null $name Name |
||
533 | * @return $this |
||
534 | */ |
||
535 | public function addBcc($email, $name = null) |
||
539 | |||
540 | /** |
||
541 | * Charset setter/getter |
||
542 | * |
||
543 | * @param string|null $charset Character set. |
||
544 | * @return string this->charset |
||
545 | */ |
||
546 | public function charset($charset = null) |
||
547 | { |
||
548 | if ($charset === null) { |
||
549 | return $this->charset; |
||
550 | } |
||
551 | $this->charset = $charset; |
||
552 | if (empty($this->headerCharset)) { |
||
553 | $this->headerCharset = $charset; |
||
554 | } |
||
555 | return $this->charset; |
||
556 | } |
||
557 | |||
558 | /** |
||
559 | * HeaderCharset setter/getter |
||
560 | * |
||
561 | * @param string|null $charset Character set. |
||
562 | * @return string this->charset |
||
563 | */ |
||
564 | public function headerCharset($charset = null) |
||
565 | { |
||
566 | if ($charset === null) { |
||
567 | return $this->headerCharset; |
||
568 | } |
||
569 | return $this->headerCharset = $charset; |
||
570 | } |
||
571 | |||
572 | /** |
||
573 | * EmailPattern setter/getter |
||
574 | * |
||
575 | * @param string|bool|null $regex The pattern to use for email address validation, |
||
576 | * null to unset the pattern and make use of filter_var() instead, false or |
||
577 | * nothing to return the current value |
||
578 | * @return string|$this |
||
579 | */ |
||
580 | public function emailPattern($regex = false) |
||
581 | { |
||
582 | if ($regex === false) { |
||
583 | return $this->_emailPattern; |
||
584 | } |
||
585 | $this->_emailPattern = $regex; |
||
|
|||
586 | return $this; |
||
587 | } |
||
588 | |||
589 | /** |
||
590 | * Set email |
||
591 | * |
||
592 | * @param string $varName Property name |
||
593 | * @param string|array $email String with email, |
||
594 | * Array with email as key, name as value or email as value (without name) |
||
595 | * @param string $name Name |
||
596 | * @return $this |
||
597 | * @throws \InvalidArgumentException |
||
598 | */ |
||
599 | View Code Duplication | protected function _setEmail($varName, $email, $name) |
|
600 | { |
||
601 | if (!is_array($email)) { |
||
602 | $this->_validateEmail($email); |
||
603 | if ($name === null) { |
||
604 | $name = $email; |
||
605 | } |
||
606 | $this->{$varName} = [$email => $name]; |
||
607 | return $this; |
||
608 | } |
||
609 | $list = []; |
||
610 | foreach ($email as $key => $value) { |
||
611 | if (is_int($key)) { |
||
612 | $key = $value; |
||
613 | } |
||
614 | $this->_validateEmail($key); |
||
615 | $list[$key] = $value; |
||
616 | } |
||
617 | $this->{$varName} = $list; |
||
618 | return $this; |
||
619 | } |
||
620 | |||
621 | /** |
||
622 | * Validate email address |
||
623 | * |
||
624 | * @param string $email Email address to validate |
||
625 | * @return void |
||
626 | * @throws \InvalidArgumentException If email address does not validate |
||
627 | */ |
||
628 | protected function _validateEmail($email) |
||
639 | |||
640 | /** |
||
641 | * Set only 1 email |
||
642 | * |
||
643 | * @param string $varName Property name |
||
644 | * @param string|array $email String with email, |
||
645 | * Array with email as key, name as value or email as value (without name) |
||
646 | * @param string $name Name |
||
647 | * @param string $throwMessage Exception message |
||
648 | * @return $this |
||
649 | * @throws \InvalidArgumentException |
||
650 | */ |
||
651 | protected function _setEmailSingle($varName, $email, $name, $throwMessage) |
||
652 | { |
||
653 | $current = $this->{$varName}; |
||
654 | $this->_setEmail($varName, $email, $name); |
||
655 | if (count($this->{$varName}) !== 1) { |
||
656 | $this->{$varName} = $current; |
||
657 | throw new InvalidArgumentException($throwMessage); |
||
658 | } |
||
659 | return $this; |
||
660 | } |
||
661 | |||
662 | /** |
||
663 | * Add email |
||
664 | * |
||
665 | * @param string $varName Property name |
||
666 | * @param string|array $email String with email, |
||
667 | * Array with email as key, name as value or email as value (without name) |
||
668 | * @param string $name Name |
||
669 | * @return $this |
||
670 | * @throws \InvalidArgumentException |
||
671 | */ |
||
672 | View Code Duplication | protected function _addEmail($varName, $email, $name) |
|
673 | { |
||
674 | if (!is_array($email)) { |
||
675 | $this->_validateEmail($email); |
||
676 | if ($name === null) { |
||
677 | $name = $email; |
||
678 | } |
||
679 | $this->{$varName}[$email] = $name; |
||
680 | return $this; |
||
681 | } |
||
682 | $list = []; |
||
683 | foreach ($email as $key => $value) { |
||
684 | if (is_int($key)) { |
||
685 | $key = $value; |
||
686 | } |
||
687 | $this->_validateEmail($key); |
||
688 | $list[$key] = $value; |
||
689 | } |
||
690 | $this->{$varName} = array_merge($this->{$varName}, $list); |
||
691 | return $this; |
||
692 | } |
||
693 | |||
694 | /** |
||
695 | * Get/Set Subject. |
||
696 | * |
||
697 | * @param string|null $subject Subject string. |
||
698 | * @return string|$this |
||
699 | */ |
||
700 | public function subject($subject = null) |
||
701 | { |
||
702 | if ($subject === null) { |
||
703 | return $this->_subject; |
||
704 | } |
||
705 | $this->_subject = $this->_encode((string)$subject); |
||
706 | return $this; |
||
707 | } |
||
708 | |||
709 | /** |
||
710 | * Sets headers for the message |
||
711 | * |
||
712 | * @param array $headers Associative array containing headers to be set. |
||
713 | * @return $this |
||
714 | */ |
||
715 | public function setHeaders(array $headers) |
||
716 | { |
||
717 | $this->_headers = $headers; |
||
718 | return $this; |
||
719 | } |
||
720 | |||
721 | /** |
||
722 | * Add header for the message |
||
723 | * |
||
724 | * @param array $headers Headers to set. |
||
725 | * @return $this |
||
726 | */ |
||
727 | public function addHeaders(array $headers) |
||
728 | { |
||
729 | $this->_headers = array_merge($this->_headers, $headers); |
||
730 | return $this; |
||
731 | } |
||
732 | |||
733 | /** |
||
734 | * Get list of headers |
||
735 | * |
||
736 | * ### Includes: |
||
737 | * |
||
738 | * - `from` |
||
739 | * - `replyTo` |
||
740 | * - `readReceipt` |
||
741 | * - `returnPath` |
||
742 | * - `to` |
||
743 | * - `cc` |
||
744 | * - `bcc` |
||
745 | * - `subject` |
||
746 | * |
||
747 | * @param array $include List of headers. |
||
748 | * @return array |
||
749 | */ |
||
750 | public function getHeaders(array $include = []) |
||
751 | { |
||
752 | if ($include == array_values($include)) { |
||
753 | $include = array_fill_keys($include, true); |
||
754 | } |
||
755 | $defaults = array_fill_keys( |
||
756 | [ |
||
757 | 'from', 'sender', 'replyTo', 'readReceipt', 'returnPath', |
||
758 | 'to', 'cc', 'bcc', 'subject'], |
||
759 | false |
||
760 | ); |
||
761 | $include += $defaults; |
||
762 | |||
763 | $headers = []; |
||
764 | $relation = [ |
||
765 | 'from' => 'From', |
||
766 | 'replyTo' => 'Reply-To', |
||
767 | 'readReceipt' => 'Disposition-Notification-To', |
||
768 | 'returnPath' => 'Return-Path' |
||
769 | ]; |
||
770 | foreach ($relation as $var => $header) { |
||
771 | if ($include[$var]) { |
||
772 | $var = '_' . $var; |
||
773 | $headers[$header] = current($this->_formatAddress($this->{$var})); |
||
774 | } |
||
775 | } |
||
776 | if ($include['sender']) { |
||
777 | if (key($this->_sender) === key($this->_from)) { |
||
778 | $headers['Sender'] = ''; |
||
779 | } else { |
||
780 | $headers['Sender'] = current($this->_formatAddress($this->_sender)); |
||
781 | } |
||
782 | } |
||
783 | |||
784 | foreach (['to', 'cc', 'bcc'] as $var) { |
||
785 | if ($include[$var]) { |
||
786 | $classVar = '_' . $var; |
||
787 | $headers[ucfirst($var)] = implode(', ', $this->_formatAddress($this->{$classVar})); |
||
788 | } |
||
789 | } |
||
790 | |||
791 | $headers += $this->_headers; |
||
792 | if (!isset($headers['Date'])) { |
||
793 | $headers['Date'] = date(DATE_RFC2822); |
||
794 | } |
||
795 | if ($this->_messageId !== false) { |
||
796 | if ($this->_messageId === true) { |
||
797 | $headers['Message-ID'] = '<' . str_replace('-', '', Text::uuid()) . '@' . $this->_domain . '>'; |
||
798 | } else { |
||
799 | $headers['Message-ID'] = $this->_messageId; |
||
800 | } |
||
801 | } |
||
802 | |||
803 | if ($include['subject']) { |
||
804 | $headers['Subject'] = $this->_subject; |
||
805 | } |
||
806 | |||
807 | $headers['MIME-Version'] = '1.0'; |
||
808 | if (!empty($this->_attachments)) { |
||
809 | $headers['Content-Type'] = 'multipart/mixed; boundary="' . $this->_boundary . '"'; |
||
810 | } elseif ($this->_emailFormat === 'both') { |
||
811 | $headers['Content-Type'] = 'multipart/alternative; boundary="' . $this->_boundary . '"'; |
||
812 | } elseif ($this->_emailFormat === 'text') { |
||
813 | $headers['Content-Type'] = 'text/plain; charset=' . $this->_getContentTypeCharset(); |
||
814 | } elseif ($this->_emailFormat === 'html') { |
||
815 | $headers['Content-Type'] = 'text/html; charset=' . $this->_getContentTypeCharset(); |
||
816 | } |
||
817 | $headers['Content-Transfer-Encoding'] = $this->_getContentTransferEncoding(); |
||
818 | |||
819 | return $headers; |
||
820 | } |
||
821 | |||
822 | /** |
||
823 | * Format addresses |
||
824 | * |
||
825 | * If the address contains non alphanumeric/whitespace characters, it will |
||
826 | * be quoted as characters like `:` and `,` are known to cause issues |
||
827 | * in address header fields. |
||
828 | * |
||
829 | * @param array $address Addresses to format. |
||
830 | * @return array |
||
831 | */ |
||
832 | protected function _formatAddress($address) |
||
833 | { |
||
834 | $return = []; |
||
835 | foreach ($address as $email => $alias) { |
||
836 | if ($email === $alias) { |
||
837 | $return[] = $email; |
||
838 | } else { |
||
839 | $encoded = $this->_encode($alias); |
||
840 | if ($encoded === $alias && preg_match('/[^a-z0-9 ]/i', $encoded)) { |
||
841 | $encoded = '"' . str_replace('"', '\"', $encoded) . '"'; |
||
842 | } |
||
843 | $return[] = sprintf('%s <%s>', $encoded, $email); |
||
844 | } |
||
845 | } |
||
846 | return $return; |
||
847 | } |
||
848 | |||
849 | /** |
||
850 | * Template and layout |
||
851 | * |
||
852 | * @param bool|string $template Template name or null to not use |
||
853 | * @param bool|string $layout Layout name or null to not use |
||
854 | * @return array|$this |
||
855 | */ |
||
856 | public function template($template = false, $layout = false) |
||
857 | { |
||
858 | if ($template === false) { |
||
859 | return [ |
||
860 | 'template' => $this->viewBuilder()->template(), |
||
861 | 'layout' => $this->viewBuilder()->layout() |
||
862 | ]; |
||
863 | } |
||
864 | $this->viewBuilder()->template($template ?: ''); |
||
865 | if ($layout !== false) { |
||
866 | $this->viewBuilder()->layout($layout ?: false); |
||
867 | } |
||
868 | return $this; |
||
869 | } |
||
870 | |||
871 | /** |
||
872 | * View class for render |
||
873 | * |
||
874 | * @param string|null $viewClass View class name. |
||
875 | * @return string|$this |
||
876 | */ |
||
877 | public function viewRender($viewClass = null) |
||
878 | { |
||
879 | if ($viewClass === null) { |
||
880 | return $this->viewBuilder()->className(); |
||
881 | } |
||
882 | $this->viewBuilder()->className($viewClass); |
||
883 | return $this; |
||
884 | } |
||
885 | |||
886 | /** |
||
887 | * Variables to be set on render |
||
888 | * |
||
889 | * @param array|null $viewVars Variables to set for view. |
||
890 | * @return array|$this |
||
891 | */ |
||
892 | public function viewVars($viewVars = null) |
||
893 | { |
||
894 | if ($viewVars === null) { |
||
895 | return $this->viewVars; |
||
896 | } |
||
897 | $this->set((array)$viewVars); |
||
898 | return $this; |
||
899 | } |
||
900 | |||
901 | /** |
||
902 | * Theme to use when rendering |
||
903 | * |
||
904 | * @param string|null $theme Theme name. |
||
905 | * @return string|$this |
||
906 | */ |
||
907 | public function theme($theme = null) |
||
908 | { |
||
909 | if ($theme === null) { |
||
910 | return $this->viewBuilder()->theme(); |
||
911 | } |
||
912 | $this->viewBuilder()->theme($theme); |
||
913 | return $this; |
||
914 | } |
||
915 | |||
916 | /** |
||
917 | * Helpers to be used in render |
||
918 | * |
||
919 | * @param array|null $helpers Helpers list. |
||
920 | * @return array|$this |
||
921 | */ |
||
922 | public function helpers($helpers = null) |
||
923 | { |
||
924 | if ($helpers === null) { |
||
925 | return $this->viewBuilder()->helpers(); |
||
926 | } |
||
927 | $this->viewBuilder()->helpers((array)$helpers, false); |
||
928 | return $this; |
||
929 | } |
||
930 | |||
931 | /** |
||
932 | * Email format |
||
933 | * |
||
934 | * @param string|null $format Formatting string. |
||
935 | * @return string|$this |
||
936 | * @throws \InvalidArgumentException |
||
937 | */ |
||
938 | public function emailFormat($format = null) |
||
939 | { |
||
940 | if ($format === null) { |
||
941 | return $this->_emailFormat; |
||
942 | } |
||
943 | if (!in_array($format, $this->_emailFormatAvailable)) { |
||
944 | throw new InvalidArgumentException('Format not available.'); |
||
945 | } |
||
946 | $this->_emailFormat = $format; |
||
947 | return $this; |
||
948 | } |
||
949 | |||
950 | /** |
||
951 | * Get/set the transport. |
||
952 | * |
||
953 | * When setting the transport you can either use the name |
||
954 | * of a configured transport or supply a constructed transport. |
||
955 | * |
||
956 | * @param string|AbstractTransport|null $name Either the name of a configured |
||
957 | * transport, or a transport instance. |
||
958 | * @return \Cake\Mailer\AbstractTransport|$this |
||
959 | * @throws \LogicException When the chosen transport lacks a send method. |
||
960 | * @throws \InvalidArgumentException When $name is neither a string nor an object. |
||
961 | */ |
||
962 | public function transport($name = null) |
||
963 | { |
||
964 | if ($name === null) { |
||
965 | return $this->_transport; |
||
966 | } |
||
967 | |||
968 | if (is_string($name)) { |
||
969 | $transport = $this->_constructTransport($name); |
||
970 | } elseif (is_object($name)) { |
||
971 | $transport = $name; |
||
972 | } else { |
||
973 | throw new InvalidArgumentException( |
||
974 | sprintf('The value passed for the "$name" argument must be either a string, or an object, %s given.', gettype($name)) |
||
975 | ); |
||
976 | } |
||
977 | if (!method_exists($transport, 'send')) { |
||
978 | throw new LogicException(sprintf('The "%s" do not have send method.', get_class($transport))); |
||
979 | } |
||
980 | |||
981 | $this->_transport = $transport; |
||
982 | return $this; |
||
983 | } |
||
984 | |||
985 | /** |
||
986 | * Build a transport instance from configuration data. |
||
987 | * |
||
988 | * @param string $name The transport configuration name to build. |
||
989 | * @return \Cake\Mailer\AbstractTransport |
||
990 | * @throws \InvalidArgumentException When transport configuration is missing or invalid. |
||
991 | */ |
||
992 | protected function _constructTransport($name) |
||
993 | { |
||
994 | if (!isset(static::$_transportConfig[$name])) { |
||
995 | throw new InvalidArgumentException(sprintf('Transport config "%s" is missing.', $name)); |
||
996 | } |
||
997 | |||
998 | if (!isset(static::$_transportConfig[$name]['className'])) { |
||
999 | throw new InvalidArgumentException( |
||
1000 | sprintf('Transport config "%s" is invalid, the required `className` option is missing', $name) |
||
1001 | ); |
||
1002 | } |
||
1003 | |||
1004 | $config = static::$_transportConfig[$name]; |
||
1005 | |||
1006 | if (is_object($config['className'])) { |
||
1007 | return $config['className']; |
||
1008 | } |
||
1009 | |||
1010 | $className = App::className($config['className'], 'Mailer/Transport', 'Transport'); |
||
1011 | if (!$className) { |
||
1012 | $className = App::className($config['className'], 'Network/Email', 'Transport'); |
||
1013 | trigger_error( |
||
1014 | 'Transports in "Network/Email" are deprecated, use "Mailer/Transport" instead.', |
||
1015 | E_USER_WARNING |
||
1016 | ); |
||
1017 | } |
||
1018 | |||
1019 | if (!$className) { |
||
1020 | throw new InvalidArgumentException(sprintf('Transport class "%s" not found.', $name)); |
||
1021 | } elseif (!method_exists($className, 'send')) { |
||
1022 | throw new InvalidArgumentException(sprintf('The "%s" does not have a send() method.', $className)); |
||
1023 | } |
||
1024 | |||
1025 | unset($config['className']); |
||
1026 | return new $className($config); |
||
1027 | } |
||
1028 | |||
1029 | /** |
||
1030 | * Message-ID |
||
1031 | * |
||
1032 | * @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 |
||
1033 | * @return bool|string|$this |
||
1034 | * @throws \InvalidArgumentException |
||
1035 | */ |
||
1036 | public function messageId($message = null) |
||
1051 | |||
1052 | /** |
||
1053 | * Domain as top level (the part after @) |
||
1054 | * |
||
1055 | * @param string|null $domain Manually set the domain for CLI mailing |
||
1056 | * @return string|$this |
||
1057 | */ |
||
1058 | public function domain($domain = null) |
||
1059 | { |
||
1060 | if ($domain === null) { |
||
1061 | return $this->_domain; |
||
1062 | } |
||
1063 | $this->_domain = $domain; |
||
1064 | return $this; |
||
1065 | } |
||
1066 | |||
1067 | /** |
||
1068 | * Add attachments to the email message |
||
1069 | * |
||
1070 | * Attachments can be defined in a few forms depending on how much control you need: |
||
1071 | * |
||
1072 | * Attach a single file: |
||
1073 | * |
||
1074 | * ``` |
||
1075 | * $email->attachments('path/to/file'); |
||
1076 | * ``` |
||
1077 | * |
||
1078 | * Attach a file with a different filename: |
||
1079 | * |
||
1080 | * ``` |
||
1081 | * $email->attachments(['custom_name.txt' => 'path/to/file.txt']); |
||
1082 | * ``` |
||
1083 | * |
||
1084 | * Attach a file and specify additional properties: |
||
1085 | * |
||
1086 | * ``` |
||
1087 | * $email->attachments(['custom_name.png' => [ |
||
1088 | * 'file' => 'path/to/file', |
||
1089 | * 'mimetype' => 'image/png', |
||
1090 | * 'contentId' => 'abc123', |
||
1091 | * 'contentDisposition' => false |
||
1092 | * ] |
||
1093 | * ]); |
||
1094 | * ``` |
||
1095 | * |
||
1096 | * Attach a file from string and specify additional properties: |
||
1097 | * |
||
1098 | * ``` |
||
1099 | * $email->attachments(['custom_name.png' => [ |
||
1100 | * 'data' => file_get_contents('path/to/file'), |
||
1101 | * 'mimetype' => 'image/png' |
||
1102 | * ] |
||
1103 | * ]); |
||
1104 | * ``` |
||
1105 | * |
||
1106 | * The `contentId` key allows you to specify an inline attachment. In your email text, you |
||
1107 | * can use `<img src="cid:abc123" />` to display the image inline. |
||
1108 | * |
||
1109 | * The `contentDisposition` key allows you to disable the `Content-Disposition` header, this can improve |
||
1110 | * attachment compatibility with outlook email clients. |
||
1111 | * |
||
1112 | * @param string|array|null $attachments String with the filename or array with filenames |
||
1113 | * @return array|$this Either the array of attachments when getting or $this when setting. |
||
1114 | * @throws \InvalidArgumentException |
||
1115 | */ |
||
1116 | public function attachments($attachments = null) |
||
1117 | { |
||
1118 | if ($attachments === null) { |
||
1119 | return $this->_attachments; |
||
1120 | } |
||
1121 | $attach = []; |
||
1122 | foreach ((array)$attachments as $name => $fileInfo) { |
||
1123 | if (!is_array($fileInfo)) { |
||
1124 | $fileInfo = ['file' => $fileInfo]; |
||
1125 | } |
||
1126 | if (!isset($fileInfo['file'])) { |
||
1127 | if (!isset($fileInfo['data'])) { |
||
1128 | throw new InvalidArgumentException('No file or data specified.'); |
||
1129 | } |
||
1130 | if (is_int($name)) { |
||
1131 | throw new InvalidArgumentException('No filename specified.'); |
||
1132 | } |
||
1133 | $fileInfo['data'] = chunk_split(base64_encode($fileInfo['data']), 76, "\r\n"); |
||
1134 | } else { |
||
1135 | $fileName = $fileInfo['file']; |
||
1136 | $fileInfo['file'] = realpath($fileInfo['file']); |
||
1137 | if ($fileInfo['file'] === false || !file_exists($fileInfo['file'])) { |
||
1138 | throw new InvalidArgumentException(sprintf('File not found: "%s"', $fileName)); |
||
1139 | } |
||
1140 | if (is_int($name)) { |
||
1141 | $name = basename($fileInfo['file']); |
||
1142 | } |
||
1143 | } |
||
1144 | if (!isset($fileInfo['mimetype'])) { |
||
1145 | $fileInfo['mimetype'] = 'application/octet-stream'; |
||
1146 | } |
||
1147 | $attach[$name] = $fileInfo; |
||
1148 | } |
||
1149 | $this->_attachments = $attach; |
||
1150 | return $this; |
||
1151 | } |
||
1152 | |||
1153 | /** |
||
1154 | * Add attachments |
||
1155 | * |
||
1156 | * @param string|array $attachments String with the filename or array with filenames |
||
1157 | * @return $this |
||
1158 | * @throws \InvalidArgumentException |
||
1159 | * @see \Cake\Mailer\Email::attachments() |
||
1160 | */ |
||
1161 | public function addAttachments($attachments) |
||
1162 | { |
||
1163 | $current = $this->_attachments; |
||
1164 | $this->attachments($attachments); |
||
1165 | $this->_attachments = array_merge($current, $this->_attachments); |
||
1166 | return $this; |
||
1167 | } |
||
1168 | |||
1169 | /** |
||
1170 | * Get generated message (used by transport classes) |
||
1171 | * |
||
1172 | * @param string|null $type Use MESSAGE_* constants or null to return the full message as array |
||
1173 | * @return string|array String if have type, array if type is null |
||
1174 | */ |
||
1175 | public function message($type = null) |
||
1176 | { |
||
1177 | switch ($type) { |
||
1178 | case static::MESSAGE_HTML: |
||
1179 | return $this->_htmlMessage; |
||
1180 | case static::MESSAGE_TEXT: |
||
1181 | return $this->_textMessage; |
||
1182 | } |
||
1183 | return $this->_message; |
||
1184 | } |
||
1185 | |||
1186 | /** |
||
1187 | * Add or read transport configuration. |
||
1188 | * |
||
1189 | * Use this method to define transports to use in delivery profiles. |
||
1190 | * Once defined you cannot edit the configurations, and must use |
||
1191 | * Email::dropTransport() to flush the configuration first. |
||
1192 | * |
||
1193 | * When using an array of configuration data a new transport |
||
1194 | * will be constructed for each message sent. When using a Closure, the |
||
1195 | * closure will be evaluated for each message. |
||
1196 | * |
||
1197 | * The `className` is used to define the class to use for a transport. |
||
1198 | * It can either be a short name, or a fully qualified classname |
||
1199 | * |
||
1200 | * @param string|array $key The configuration name to read/write. Or |
||
1201 | * an array of multiple transports to set. |
||
1202 | * @param array|AbstractTransport|null $config Either an array of configuration |
||
1203 | * data, or a transport instance. |
||
1204 | * @return mixed Either null when setting or an array of data when reading. |
||
1205 | * @throws \BadMethodCallException When modifying an existing configuration. |
||
1206 | */ |
||
1207 | public static function configTransport($key, $config = null) |
||
1208 | { |
||
1209 | if ($config === null && is_string($key)) { |
||
1210 | return isset(static::$_transportConfig[$key]) ? static::$_transportConfig[$key] : null; |
||
1211 | } |
||
1212 | if ($config === null && is_array($key)) { |
||
1213 | foreach ($key as $name => $settings) { |
||
1214 | static::configTransport($name, $settings); |
||
1215 | } |
||
1216 | return; |
||
1217 | } |
||
1218 | if (isset(static::$_transportConfig[$key])) { |
||
1219 | throw new BadMethodCallException(sprintf('Cannot modify an existing config "%s"', $key)); |
||
1220 | } |
||
1221 | |||
1222 | if (is_object($config)) { |
||
1223 | $config = ['className' => $config]; |
||
1224 | } |
||
1225 | |||
1226 | View Code Duplication | if (isset($config['url'])) { |
|
1227 | $parsed = static::parseDsn($config['url']); |
||
1228 | unset($config['url']); |
||
1229 | $config = $parsed + $config; |
||
1230 | } |
||
1231 | |||
1232 | static::$_transportConfig[$key] = $config; |
||
1233 | } |
||
1234 | |||
1235 | /** |
||
1236 | * Returns an array containing the named transport configurations |
||
1237 | * |
||
1238 | * @return array Array of configurations. |
||
1239 | */ |
||
1240 | public static function configuredTransport() |
||
1244 | |||
1245 | /** |
||
1246 | * Delete transport configuration. |
||
1247 | * |
||
1248 | * @param string $key The transport name to remove. |
||
1249 | * @return void |
||
1250 | */ |
||
1251 | public static function dropTransport($key) |
||
1255 | |||
1256 | /** |
||
1257 | * Get/Set the configuration profile to use for this instance. |
||
1258 | * |
||
1259 | * @param null|string|array $config String with configuration name, or |
||
1260 | * an array with config or null to return current config. |
||
1261 | * @return string|array|$this |
||
1262 | */ |
||
1263 | public function profile($config = null) |
||
1264 | { |
||
1265 | if ($config === null) { |
||
1266 | return $this->_profile; |
||
1267 | } |
||
1268 | if (!is_array($config)) { |
||
1269 | $config = (string)$config; |
||
1270 | } |
||
1271 | $this->_applyConfig($config); |
||
1272 | return $this; |
||
1273 | } |
||
1274 | |||
1275 | /** |
||
1276 | * Send an email using the specified content, template and layout |
||
1277 | * |
||
1278 | * @param string|array|null $content String with message or array with messages |
||
1279 | * @return array |
||
1280 | * @throws \BadMethodCallException |
||
1281 | */ |
||
1282 | public function send($content = null) |
||
1283 | { |
||
1284 | if (empty($this->_from)) { |
||
1285 | throw new BadMethodCallException('From is not specified.'); |
||
1286 | } |
||
1287 | if (empty($this->_to) && empty($this->_cc) && empty($this->_bcc)) { |
||
1288 | throw new BadMethodCallException('You need specify one destination on to, cc or bcc.'); |
||
1289 | } |
||
1290 | |||
1291 | if (is_array($content)) { |
||
1292 | $content = implode("\n", $content) . "\n"; |
||
1293 | } |
||
1294 | |||
1295 | $this->_message = $this->_render($this->_wrap($content)); |
||
1296 | |||
1297 | $transport = $this->transport(); |
||
1298 | if (!$transport) { |
||
1299 | $msg = 'Cannot send email, transport was not defined. Did you call transport() or define ' . |
||
1300 | ' a transport in the set profile?'; |
||
1301 | throw new BadMethodCallException($msg); |
||
1302 | } |
||
1303 | $contents = $transport->send($this); |
||
1304 | $this->_logDelivery($contents); |
||
1305 | return $contents; |
||
1306 | } |
||
1307 | |||
1308 | /** |
||
1309 | * Log the email message delivery. |
||
1310 | * |
||
1311 | * @param array $contents The content with 'headers' and 'message' keys. |
||
1312 | * @return void |
||
1313 | */ |
||
1314 | protected function _logDelivery($contents) |
||
1315 | { |
||
1316 | if (empty($this->_profile['log'])) { |
||
1317 | return; |
||
1318 | } |
||
1319 | $config = [ |
||
1320 | 'level' => 'debug', |
||
1321 | 'scope' => 'email' |
||
1322 | ]; |
||
1323 | if ($this->_profile['log'] !== true) { |
||
1324 | if (!is_array($this->_profile['log'])) { |
||
1325 | $this->_profile['log'] = ['level' => $this->_profile['log']]; |
||
1326 | } |
||
1327 | $config = $this->_profile['log'] + $config; |
||
1328 | } |
||
1329 | Log::write( |
||
1330 | $config['level'], |
||
1331 | PHP_EOL . $contents['headers'] . PHP_EOL . $contents['message'], |
||
1332 | $config['scope'] |
||
1333 | ); |
||
1334 | } |
||
1335 | |||
1336 | /** |
||
1337 | * Static method to fast create an instance of \Cake\Mailer\Email |
||
1338 | * |
||
1339 | * @param string|array $to Address to send (see Cake\Mailer\Email::to()). If null, will try to use 'to' from transport config |
||
1340 | * @param string $subject String of subject or null to use 'subject' from transport config |
||
1341 | * @param string|array $message String with message or array with variables to be used in render |
||
1342 | * @param string|array $transportConfig String to use config from EmailConfig or array with configs |
||
1343 | * @param bool $send Send the email or just return the instance pre-configured |
||
1344 | * @return \Cake\Mailer\Email Instance of Cake\Mailer\Email |
||
1345 | * @throws \InvalidArgumentException |
||
1346 | */ |
||
1347 | public static function deliver($to = null, $subject = null, $message = null, $transportConfig = 'fast', $send = true) |
||
1348 | { |
||
1349 | $class = __CLASS__; |
||
1350 | $instance = new $class($transportConfig); |
||
1351 | if ($to !== null) { |
||
1352 | $instance->to($to); |
||
1353 | } |
||
1354 | if ($subject !== null) { |
||
1355 | $instance->subject($subject); |
||
1356 | } |
||
1357 | if (is_array($message)) { |
||
1358 | $instance->viewVars($message); |
||
1359 | $message = null; |
||
1360 | } elseif ($message === null && array_key_exists('message', $config = $instance->profile())) { |
||
1361 | $message = $config['message']; |
||
1362 | } |
||
1363 | |||
1364 | if ($send === true) { |
||
1365 | $instance->send($message); |
||
1366 | } |
||
1367 | |||
1368 | return $instance; |
||
1369 | } |
||
1370 | |||
1371 | /** |
||
1372 | * Apply the config to an instance |
||
1373 | * |
||
1374 | * @param string|array $config Configuration options. |
||
1375 | * @return void |
||
1376 | * @throws \InvalidArgumentException When using a configuration that doesn't exist. |
||
1377 | */ |
||
1378 | protected function _applyConfig($config) |
||
1379 | { |
||
1380 | View Code Duplication | if (is_string($config)) { |
|
1381 | $name = $config; |
||
1382 | $config = static::config($name); |
||
1383 | if (empty($config)) { |
||
1384 | throw new InvalidArgumentException(sprintf('Unknown email configuration "%s".', $name)); |
||
1385 | } |
||
1386 | unset($name); |
||
1387 | } |
||
1388 | |||
1389 | $this->_profile = array_merge($this->_profile, $config); |
||
1390 | |||
1391 | $simpleMethods = [ |
||
1392 | 'from', 'sender', 'to', 'replyTo', 'readReceipt', 'returnPath', |
||
1393 | 'cc', 'bcc', 'messageId', 'domain', 'subject', 'attachments', |
||
1394 | 'transport', 'emailFormat', 'emailPattern', 'charset', 'headerCharset' |
||
1395 | ]; |
||
1396 | foreach ($simpleMethods as $method) { |
||
1397 | if (isset($config[$method])) { |
||
1398 | $this->$method($config[$method]); |
||
1399 | } |
||
1400 | } |
||
1401 | |||
1402 | if (empty($this->headerCharset)) { |
||
1403 | $this->headerCharset = $this->charset; |
||
1404 | } |
||
1405 | if (isset($config['headers'])) { |
||
1406 | $this->setHeaders($config['headers']); |
||
1407 | } |
||
1408 | |||
1409 | $viewBuilderMethods = [ |
||
1410 | 'template', 'layout', 'theme' |
||
1411 | ]; |
||
1412 | foreach ($viewBuilderMethods as $method) { |
||
1413 | if (array_key_exists($method, $config)) { |
||
1414 | $this->viewBuilder()->$method($config[$method]); |
||
1415 | } |
||
1416 | } |
||
1417 | |||
1418 | if (array_key_exists('helpers', $config)) { |
||
1419 | $this->viewBuilder()->helpers($config['helpers'], false); |
||
1420 | } |
||
1421 | if (array_key_exists('viewRender', $config)) { |
||
1422 | $this->viewBuilder()->className($config['viewRender']); |
||
1423 | } |
||
1424 | if (array_key_exists('viewVars', $config)) { |
||
1425 | $this->set($config['viewVars']); |
||
1426 | } |
||
1427 | } |
||
1428 | |||
1429 | /** |
||
1430 | * Reset all the internal variables to be able to send out a new email. |
||
1431 | * |
||
1432 | * @return $this |
||
1433 | */ |
||
1434 | public function reset() |
||
1435 | { |
||
1436 | $this->_to = []; |
||
1437 | $this->_from = []; |
||
1438 | $this->_sender = []; |
||
1439 | $this->_replyTo = []; |
||
1440 | $this->_readReceipt = []; |
||
1441 | $this->_returnPath = []; |
||
1442 | $this->_cc = []; |
||
1443 | $this->_bcc = []; |
||
1444 | $this->_messageId = true; |
||
1445 | $this->_subject = ''; |
||
1446 | $this->_headers = []; |
||
1447 | $this->_textMessage = ''; |
||
1448 | $this->_htmlMessage = ''; |
||
1449 | $this->_message = ''; |
||
1450 | $this->_emailFormat = 'text'; |
||
1451 | $this->_transport = null; |
||
1452 | $this->charset = 'utf-8'; |
||
1453 | $this->headerCharset = null; |
||
1454 | $this->_attachments = []; |
||
1455 | $this->_profile = []; |
||
1456 | $this->_emailPattern = self::EMAIL_PATTERN; |
||
1457 | |||
1458 | $this->viewBuilder()->layout('default'); |
||
1459 | $this->viewBuilder()->template(''); |
||
1460 | $this->viewBuilder()->classname('Cake\View\View'); |
||
1461 | $this->viewVars = []; |
||
1462 | $this->viewBuilder()->theme(false); |
||
1463 | $this->viewBuilder()->helpers(['Html'], false); |
||
1464 | |||
1465 | return $this; |
||
1466 | } |
||
1467 | |||
1468 | /** |
||
1469 | * Encode the specified string using the current charset |
||
1470 | * |
||
1471 | * @param string $text String to encode |
||
1472 | * @return string Encoded string |
||
1473 | */ |
||
1474 | protected function _encode($text) |
||
1475 | { |
||
1476 | $restore = mb_internal_encoding(); |
||
1477 | mb_internal_encoding($this->_appCharset); |
||
1478 | if (empty($this->headerCharset)) { |
||
1479 | $this->headerCharset = $this->charset; |
||
1480 | } |
||
1481 | $return = mb_encode_mimeheader($text, $this->headerCharset, 'B'); |
||
1482 | mb_internal_encoding($restore); |
||
1483 | return $return; |
||
1484 | } |
||
1485 | |||
1486 | /** |
||
1487 | * Translates a string for one charset to another if the App.encoding value |
||
1488 | * differs and the mb_convert_encoding function exists |
||
1489 | * |
||
1490 | * @param string $text The text to be converted |
||
1491 | * @param string $charset the target encoding |
||
1492 | * @return string |
||
1493 | */ |
||
1494 | protected function _encodeString($text, $charset) |
||
1495 | { |
||
1496 | if ($this->_appCharset === $charset) { |
||
1497 | return $text; |
||
1498 | } |
||
1499 | return mb_convert_encoding($text, $charset, $this->_appCharset); |
||
1500 | } |
||
1501 | |||
1502 | /** |
||
1503 | * Wrap the message to follow the RFC 2822 - 2.1.1 |
||
1504 | * |
||
1505 | * @param string $message Message to wrap |
||
1506 | * @param int $wrapLength The line length |
||
1507 | * @return array Wrapped message |
||
1508 | */ |
||
1509 | protected function _wrap($message, $wrapLength = Email::LINE_LENGTH_MUST) |
||
1510 | { |
||
1511 | if (strlen($message) === 0) { |
||
1512 | return ['']; |
||
1513 | } |
||
1514 | $message = str_replace(["\r\n", "\r"], "\n", $message); |
||
1515 | $lines = explode("\n", $message); |
||
1516 | $formatted = []; |
||
1517 | $cut = ($wrapLength == Email::LINE_LENGTH_MUST); |
||
1518 | |||
1519 | foreach ($lines as $line) { |
||
1520 | if (empty($line) && $line !== '0') { |
||
1521 | $formatted[] = ''; |
||
1522 | continue; |
||
1523 | } |
||
1524 | if (strlen($line) < $wrapLength) { |
||
1525 | $formatted[] = $line; |
||
1526 | continue; |
||
1527 | } |
||
1528 | if (!preg_match('/<[a-z]+.*>/i', $line)) { |
||
1529 | $formatted = array_merge( |
||
1530 | $formatted, |
||
1531 | explode("\n", wordwrap($line, $wrapLength, "\n", $cut)) |
||
1532 | ); |
||
1533 | continue; |
||
1534 | } |
||
1535 | |||
1536 | $tagOpen = false; |
||
1537 | $tmpLine = $tag = ''; |
||
1538 | $tmpLineLength = 0; |
||
1539 | for ($i = 0, $count = strlen($line); $i < $count; $i++) { |
||
1540 | $char = $line[$i]; |
||
1541 | if ($tagOpen) { |
||
1542 | $tag .= $char; |
||
1543 | if ($char === '>') { |
||
1544 | $tagLength = strlen($tag); |
||
1545 | if ($tagLength + $tmpLineLength < $wrapLength) { |
||
1546 | $tmpLine .= $tag; |
||
1547 | $tmpLineLength += $tagLength; |
||
1548 | } else { |
||
1549 | if ($tmpLineLength > 0) { |
||
1550 | $formatted = array_merge( |
||
1551 | $formatted, |
||
1552 | explode("\n", wordwrap(trim($tmpLine), $wrapLength, "\n", $cut)) |
||
1553 | ); |
||
1554 | $tmpLine = ''; |
||
1555 | $tmpLineLength = 0; |
||
1556 | } |
||
1557 | if ($tagLength > $wrapLength) { |
||
1558 | $formatted[] = $tag; |
||
1559 | } else { |
||
1560 | $tmpLine = $tag; |
||
1561 | $tmpLineLength = $tagLength; |
||
1562 | } |
||
1563 | } |
||
1564 | $tag = ''; |
||
1565 | $tagOpen = false; |
||
1566 | } |
||
1567 | continue; |
||
1568 | } |
||
1569 | if ($char === '<') { |
||
1570 | $tagOpen = true; |
||
1571 | $tag = '<'; |
||
1572 | continue; |
||
1573 | } |
||
1574 | if ($char === ' ' && $tmpLineLength >= $wrapLength) { |
||
1575 | $formatted[] = $tmpLine; |
||
1576 | $tmpLineLength = 0; |
||
1577 | continue; |
||
1578 | } |
||
1579 | $tmpLine .= $char; |
||
1580 | $tmpLineLength++; |
||
1581 | if ($tmpLineLength === $wrapLength) { |
||
1582 | $nextChar = $line[$i + 1]; |
||
1583 | if ($nextChar === ' ' || $nextChar === '<') { |
||
1584 | $formatted[] = trim($tmpLine); |
||
1585 | $tmpLine = ''; |
||
1586 | $tmpLineLength = 0; |
||
1587 | if ($nextChar === ' ') { |
||
1588 | $i++; |
||
1589 | } |
||
1590 | } else { |
||
1591 | $lastSpace = strrpos($tmpLine, ' '); |
||
1592 | if ($lastSpace === false) { |
||
1593 | continue; |
||
1594 | } |
||
1595 | $formatted[] = trim(substr($tmpLine, 0, $lastSpace)); |
||
1596 | $tmpLine = substr($tmpLine, $lastSpace + 1); |
||
1597 | |||
1598 | $tmpLineLength = strlen($tmpLine); |
||
1599 | } |
||
1600 | } |
||
1601 | } |
||
1602 | if (!empty($tmpLine)) { |
||
1603 | $formatted[] = $tmpLine; |
||
1604 | } |
||
1605 | } |
||
1606 | $formatted[] = ''; |
||
1607 | return $formatted; |
||
1608 | } |
||
1609 | |||
1610 | /** |
||
1611 | * Create unique boundary identifier |
||
1612 | * |
||
1613 | * @return void |
||
1614 | */ |
||
1615 | protected function _createBoundary() |
||
1616 | { |
||
1617 | if (!empty($this->_attachments) || $this->_emailFormat === 'both') { |
||
1618 | $this->_boundary = md5(uniqid(time())); |
||
1619 | } |
||
1620 | } |
||
1621 | |||
1622 | /** |
||
1623 | * Attach non-embedded files by adding file contents inside boundaries. |
||
1624 | * |
||
1625 | * @param string|null $boundary Boundary to use. If null, will default to $this->_boundary |
||
1626 | * @return array An array of lines to add to the message |
||
1627 | */ |
||
1628 | protected function _attachFiles($boundary = null) |
||
1629 | { |
||
1630 | if ($boundary === null) { |
||
1631 | $boundary = $this->_boundary; |
||
1632 | } |
||
1633 | |||
1634 | $msg = []; |
||
1635 | foreach ($this->_attachments as $filename => $fileInfo) { |
||
1636 | if (!empty($fileInfo['contentId'])) { |
||
1637 | continue; |
||
1638 | } |
||
1639 | $data = isset($fileInfo['data']) ? $fileInfo['data'] : $this->_readFile($fileInfo['file']); |
||
1640 | $hasDisposition = ( |
||
1641 | !isset($fileInfo['contentDisposition']) || |
||
1642 | $fileInfo['contentDisposition'] |
||
1643 | ); |
||
1644 | $part = new Part(false, $data, false); |
||
1645 | |||
1646 | if ($hasDisposition) { |
||
1647 | $part->disposition('attachment'); |
||
1648 | $part->filename($filename); |
||
1649 | } |
||
1650 | $part->transferEncoding('base64'); |
||
1651 | $part->type($fileInfo['mimetype']); |
||
1652 | |||
1653 | $msg[] = '--' . $boundary; |
||
1654 | $msg[] = (string)$part; |
||
1655 | $msg[] = ''; |
||
1656 | } |
||
1657 | return $msg; |
||
1658 | } |
||
1659 | |||
1660 | /** |
||
1661 | * Read the file contents and return a base64 version of the file contents. |
||
1662 | * |
||
1663 | * @param string $path The absolute path to the file to read. |
||
1664 | * @return string File contents in base64 encoding |
||
1665 | */ |
||
1666 | protected function _readFile($path) |
||
1667 | { |
||
1668 | $File = new File($path); |
||
1669 | return chunk_split(base64_encode($File->read())); |
||
1670 | } |
||
1671 | |||
1672 | /** |
||
1673 | * Attach inline/embedded files to the message. |
||
1674 | * |
||
1675 | * @param string|null $boundary Boundary to use. If null, will default to $this->_boundary |
||
1676 | * @return array An array of lines to add to the message |
||
1677 | */ |
||
1678 | protected function _attachInlineFiles($boundary = null) |
||
1679 | { |
||
1680 | if ($boundary === null) { |
||
1681 | $boundary = $this->_boundary; |
||
1682 | } |
||
1683 | |||
1684 | $msg = []; |
||
1685 | foreach ($this->_attachments as $filename => $fileInfo) { |
||
1686 | if (empty($fileInfo['contentId'])) { |
||
1687 | continue; |
||
1688 | } |
||
1689 | $data = isset($fileInfo['data']) ? $fileInfo['data'] : $this->_readFile($fileInfo['file']); |
||
1690 | |||
1691 | $msg[] = '--' . $boundary; |
||
1692 | $part = new Part(false, $data, 'inline'); |
||
1693 | $part->type($fileInfo['mimetype']); |
||
1694 | $part->transferEncoding('base64'); |
||
1695 | $part->contentId($fileInfo['contentId']); |
||
1696 | $part->filename($filename); |
||
1697 | $msg[] = (string)$part; |
||
1698 | $msg[] = ''; |
||
1699 | } |
||
1700 | return $msg; |
||
1701 | } |
||
1702 | |||
1703 | /** |
||
1704 | * Render the body of the email. |
||
1705 | * |
||
1706 | * @param array $content Content to render |
||
1707 | * @return array Email body ready to be sent |
||
1708 | */ |
||
1709 | protected function _render($content) |
||
1710 | { |
||
1711 | $this->_textMessage = $this->_htmlMessage = ''; |
||
1712 | |||
1713 | $content = implode("\n", $content); |
||
1714 | $rendered = $this->_renderTemplates($content); |
||
1715 | |||
1716 | $this->_createBoundary(); |
||
1717 | $msg = []; |
||
1718 | |||
1719 | $contentIds = array_filter((array)Hash::extract($this->_attachments, '{s}.contentId')); |
||
1720 | $hasInlineAttachments = count($contentIds) > 0; |
||
1721 | $hasAttachments = !empty($this->_attachments); |
||
1722 | $hasMultipleTypes = count($rendered) > 1; |
||
1723 | $multiPart = ($hasAttachments || $hasMultipleTypes); |
||
1724 | |||
1725 | $boundary = $relBoundary = $textBoundary = $this->_boundary; |
||
1726 | |||
1727 | View Code Duplication | if ($hasInlineAttachments) { |
|
1728 | $msg[] = '--' . $boundary; |
||
1729 | $msg[] = 'Content-Type: multipart/related; boundary="rel-' . $boundary . '"'; |
||
1730 | $msg[] = ''; |
||
1731 | $relBoundary = $textBoundary = 'rel-' . $boundary; |
||
1732 | } |
||
1733 | |||
1734 | View Code Duplication | if ($hasMultipleTypes && $hasAttachments) { |
|
1735 | $msg[] = '--' . $relBoundary; |
||
1736 | $msg[] = 'Content-Type: multipart/alternative; boundary="alt-' . $boundary . '"'; |
||
1737 | $msg[] = ''; |
||
1738 | $textBoundary = 'alt-' . $boundary; |
||
1739 | } |
||
1740 | |||
1741 | View Code Duplication | if (isset($rendered['text'])) { |
|
1742 | if ($multiPart) { |
||
1743 | $msg[] = '--' . $textBoundary; |
||
1744 | $msg[] = 'Content-Type: text/plain; charset=' . $this->_getContentTypeCharset(); |
||
1745 | $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding(); |
||
1746 | $msg[] = ''; |
||
1747 | } |
||
1748 | $this->_textMessage = $rendered['text']; |
||
1749 | $content = explode("\n", $this->_textMessage); |
||
1750 | $msg = array_merge($msg, $content); |
||
1751 | $msg[] = ''; |
||
1752 | } |
||
1753 | |||
1754 | View Code Duplication | if (isset($rendered['html'])) { |
|
1755 | if ($multiPart) { |
||
1756 | $msg[] = '--' . $textBoundary; |
||
1757 | $msg[] = 'Content-Type: text/html; charset=' . $this->_getContentTypeCharset(); |
||
1758 | $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding(); |
||
1759 | $msg[] = ''; |
||
1760 | } |
||
1761 | $this->_htmlMessage = $rendered['html']; |
||
1762 | $content = explode("\n", $this->_htmlMessage); |
||
1763 | $msg = array_merge($msg, $content); |
||
1764 | $msg[] = ''; |
||
1765 | } |
||
1766 | |||
1767 | if ($textBoundary !== $relBoundary) { |
||
1768 | $msg[] = '--' . $textBoundary . '--'; |
||
1769 | $msg[] = ''; |
||
1770 | } |
||
1771 | |||
1772 | if ($hasInlineAttachments) { |
||
1773 | $attachments = $this->_attachInlineFiles($relBoundary); |
||
1774 | $msg = array_merge($msg, $attachments); |
||
1775 | $msg[] = ''; |
||
1776 | $msg[] = '--' . $relBoundary . '--'; |
||
1777 | $msg[] = ''; |
||
1778 | } |
||
1779 | |||
1780 | if ($hasAttachments) { |
||
1781 | $attachments = $this->_attachFiles($boundary); |
||
1782 | $msg = array_merge($msg, $attachments); |
||
1783 | } |
||
1784 | if ($hasAttachments || $hasMultipleTypes) { |
||
1785 | $msg[] = ''; |
||
1786 | $msg[] = '--' . $boundary . '--'; |
||
1787 | $msg[] = ''; |
||
1788 | } |
||
1789 | return $msg; |
||
1790 | } |
||
1791 | |||
1792 | /** |
||
1793 | * Gets the text body types that are in this email message |
||
1794 | * |
||
1795 | * @return array Array of types. Valid types are 'text' and 'html' |
||
1796 | */ |
||
1797 | protected function _getTypes() |
||
1798 | { |
||
1799 | $types = [$this->_emailFormat]; |
||
1800 | if ($this->_emailFormat === 'both') { |
||
1801 | $types = ['html', 'text']; |
||
1802 | } |
||
1803 | return $types; |
||
1804 | } |
||
1805 | |||
1806 | /** |
||
1807 | * Build and set all the view properties needed to render the templated emails. |
||
1808 | * If there is no template set, the $content will be returned in a hash |
||
1809 | * of the text content types for the email. |
||
1810 | * |
||
1811 | * @param string $content The content passed in from send() in most cases. |
||
1812 | * @return array The rendered content with html and text keys. |
||
1813 | */ |
||
1814 | protected function _renderTemplates($content) |
||
1815 | { |
||
1816 | $types = $this->_getTypes(); |
||
1817 | $rendered = []; |
||
1818 | $template = $this->viewBuilder()->template(); |
||
1819 | if (empty($template)) { |
||
1820 | foreach ($types as $type) { |
||
1821 | $rendered[$type] = $this->_encodeString($content, $this->charset); |
||
1822 | } |
||
1823 | return $rendered; |
||
1824 | } |
||
1825 | |||
1826 | $View = $this->createView(); |
||
1827 | |||
1828 | list($templatePlugin) = pluginSplit($View->template()); |
||
1829 | list($layoutPlugin) = pluginSplit($View->layout()); |
||
1830 | if ($templatePlugin) { |
||
1831 | $View->plugin = $templatePlugin; |
||
1832 | } elseif ($layoutPlugin) { |
||
1833 | $View->plugin = $layoutPlugin; |
||
1834 | } |
||
1835 | |||
1836 | if ($View->get('content') === null) { |
||
1837 | $View->set('content', $content); |
||
1838 | } |
||
1839 | |||
1840 | foreach ($types as $type) { |
||
1841 | $View->hasRendered = false; |
||
1842 | $View->templatePath('Email' . DIRECTORY_SEPARATOR . $type); |
||
1843 | $View->layoutPath('Email' . DIRECTORY_SEPARATOR . $type); |
||
1844 | |||
1845 | $render = $View->render(); |
||
1846 | $render = str_replace(["\r\n", "\r"], "\n", $render); |
||
1847 | $rendered[$type] = $this->_encodeString($render, $this->charset); |
||
1848 | } |
||
1849 | |||
1850 | foreach ($rendered as $type => $content) { |
||
1851 | $rendered[$type] = $this->_wrap($content); |
||
1852 | $rendered[$type] = implode("\n", $rendered[$type]); |
||
1853 | $rendered[$type] = rtrim($rendered[$type], "\n"); |
||
1854 | } |
||
1855 | return $rendered; |
||
1856 | } |
||
1857 | |||
1858 | /** |
||
1859 | * Return the Content-Transfer Encoding value based on the set charset |
||
1860 | * |
||
1861 | * @return string |
||
1862 | */ |
||
1863 | protected function _getContentTransferEncoding() |
||
1864 | { |
||
1865 | $charset = strtoupper($this->charset); |
||
1866 | if (in_array($charset, $this->_charset8bit)) { |
||
1867 | return '8bit'; |
||
1868 | } |
||
1869 | return '7bit'; |
||
1870 | } |
||
1871 | |||
1872 | /** |
||
1873 | * Return charset value for Content-Type. |
||
1874 | * |
||
1875 | * Checks fallback/compatibility types which include workarounds |
||
1876 | * for legacy japanese character sets. |
||
1877 | * |
||
1878 | * @return string |
||
1879 | */ |
||
1880 | protected function _getContentTypeCharset() |
||
1881 | { |
||
1882 | $charset = strtoupper($this->charset); |
||
1883 | if (array_key_exists($charset, $this->_contentTypeCharset)) { |
||
1884 | return strtoupper($this->_contentTypeCharset[$charset]); |
||
1885 | } |
||
1886 | return strtoupper($this->charset); |
||
1887 | } |
||
1888 | |||
1889 | /** |
||
1890 | * Serializes the email object to a value that can be natively serialized and re-used |
||
1891 | * to clone this email instance. |
||
1892 | * |
||
1893 | * It has certain limitations for viewVars that are good to know: |
||
1894 | * |
||
1895 | * - ORM\Query executed and stored as resultset |
||
1896 | * - SimpleXmlElements stored as associative array |
||
1897 | * - Exceptions stored as strings |
||
1898 | * - Resources, \Closure and \PDO are not supported. |
||
1899 | * |
||
1900 | * @return array Serializable array of configuration properties. |
||
1901 | * @throws \Exception When a view var object can not be properly serialized. |
||
1902 | */ |
||
1903 | public function jsonSerialize() |
||
1904 | { |
||
1905 | $properties = [ |
||
1906 | '_to', '_from', '_sender', '_replyTo', '_cc', '_bcc', '_subject', |
||
1907 | '_returnPath', '_readReceipt', '_emailFormat', '_emailPattern', '_domain', |
||
1908 | '_attachments', '_messageId', '_headers', '_appCharset', 'viewVars', 'charset', 'headerCharset' |
||
1909 | ]; |
||
1910 | |||
1911 | $array = ['viewConfig' => $this->viewBuilder()->jsonSerialize()]; |
||
1912 | |||
1913 | foreach ($properties as $property) { |
||
1914 | $array[$property] = $this->{$property}; |
||
1915 | } |
||
1916 | |||
1917 | array_walk($array['_attachments'], function (&$item, $key) { |
||
1918 | if (!empty($item['file'])) { |
||
1919 | $item['data'] = $this->_readFile($item['file']); |
||
1920 | unset($item['file']); |
||
1921 | } |
||
1922 | }); |
||
1923 | |||
1924 | array_walk_recursive($array['viewVars'], [$this, '_checkViewVars']); |
||
1925 | |||
1926 | return array_filter($array, function ($i) { |
||
1927 | return !is_array($i) && strlen($i) || !empty($i); |
||
1928 | }); |
||
1929 | } |
||
1930 | |||
1931 | /** |
||
1932 | * Iterates through hash to clean up and normalize. |
||
1933 | * |
||
1934 | * @param mixed $item Reference to the view var value. |
||
1935 | * @param string $key View var key. |
||
1936 | * @return void |
||
1937 | */ |
||
1938 | protected function _checkViewVars(&$item, $key) |
||
1939 | { |
||
1940 | if ($item instanceof Exception) { |
||
1941 | $item = (string)$item; |
||
1942 | } |
||
1943 | |||
1944 | if (is_resource($item) || |
||
1945 | $item instanceof Closure || |
||
1946 | $item instanceof PDO |
||
1947 | ) { |
||
1948 | throw new RuntimeException(sprintf( |
||
1949 | 'Failed serializing the `%s` %s in the `%s` view var', |
||
1950 | is_resource($item) ? get_resource_type($item) : get_class($item), |
||
1951 | is_resource($item) ? 'resource' : 'object', |
||
1952 | $key |
||
1953 | )); |
||
1954 | } |
||
1955 | } |
||
1956 | |||
1957 | /** |
||
1958 | * Configures an email instance object from serialized config. |
||
1959 | * |
||
1960 | * @param array $config Email configuration array. |
||
1961 | * @return \Cake\Mailer\Email Configured email instance. |
||
1962 | */ |
||
1963 | public function createFromArray($config) |
||
1964 | { |
||
1965 | if (isset($config['viewConfig'])) { |
||
1966 | $this->viewBuilder()->createFromArray($config['viewConfig']); |
||
1967 | unset($config['viewConfig']); |
||
1968 | } |
||
1969 | |||
1970 | foreach ($config as $property => $value) { |
||
1971 | $this->{$property} = $value; |
||
1972 | } |
||
1973 | |||
1974 | return $this; |
||
1975 | } |
||
1976 | |||
1977 | /** |
||
1978 | * Serializes the Email object. |
||
1979 | * |
||
1980 | * @return string |
||
1981 | */ |
||
1982 | public function serialize() |
||
1983 | { |
||
1984 | $array = $this->jsonSerialize(); |
||
1985 | array_walk_recursive($array, function (&$item, $key) { |
||
1986 | if ($item instanceof SimpleXmlElement) { |
||
1987 | $item = json_decode(json_encode((array)$item), true); |
||
1988 | } |
||
1989 | }); |
||
1990 | return serialize($array); |
||
1991 | } |
||
1992 | |||
1993 | /** |
||
1994 | * Unserializes the Email object. |
||
1995 | * |
||
1996 | * @param string $data Serialized string. |
||
1997 | * @return \Cake\Mailer\Email Configured email instance. |
||
1998 | */ |
||
1999 | public function unserialize($data) |
||
2003 | } |
||
2004 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.