1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 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: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49:
50: class Email implements JsonSerializable, Serializable
51: {
52:
53: use StaticConfigTrait;
54: use ViewVarsTrait;
55:
56: 57: 58: 59: 60:
61: const LINE_LENGTH_SHOULD = 78;
62:
63: 64: 65: 66: 67:
68: const LINE_LENGTH_MUST = 998;
69:
70: 71: 72: 73: 74:
75: const MESSAGE_HTML = 'html';
76:
77: 78: 79: 80: 81:
82: const MESSAGE_TEXT = 'text';
83:
84: 85: 86: 87: 88:
89: const EMAIL_PATTERN = '/^((?:[\p{L}0-9.!#$%&\'*+\/=?^_`{|}~-]+)*@[\p{L}0-9-._]+)$/ui';
90:
91: 92: 93: 94: 95:
96: protected $_to = [];
97:
98: 99: 100: 101: 102:
103: protected $_from = [];
104:
105: 106: 107: 108: 109:
110: protected $_sender = [];
111:
112: 113: 114: 115: 116:
117: protected $_replyTo = [];
118:
119: 120: 121: 122: 123:
124: protected $_readReceipt = [];
125:
126: 127: 128: 129: 130: 131: 132: 133:
134: protected $_returnPath = [];
135:
136: 137: 138: 139: 140: 141: 142: 143:
144: protected $_cc = [];
145:
146: 147: 148: 149: 150: 151: 152: 153:
154: protected $_bcc = [];
155:
156: 157: 158: 159: 160:
161: protected $_messageId = true;
162:
163: 164: 165: 166: 167: 168:
169: protected $_domain;
170:
171: 172: 173: 174: 175:
176: protected $_subject = '';
177:
178: 179: 180: 181: 182: 183:
184: protected $_headers = [];
185:
186: 187: 188: 189: 190:
191: protected $_textMessage = '';
192:
193: 194: 195: 196: 197:
198: protected $_htmlMessage = '';
199:
200: 201: 202: 203: 204:
205: protected $_message = [];
206:
207: 208: 209: 210: 211:
212: protected $_emailFormatAvailable = ['text', 'html', 'both'];
213:
214: 215: 216: 217: 218:
219: protected $_emailFormat = 'text';
220:
221: 222: 223: 224: 225:
226: protected $_transport;
227:
228: 229: 230: 231: 232:
233: public $charset = 'utf-8';
234:
235: 236: 237: 238: 239: 240:
241: public $headerCharset;
242:
243: 244: 245: 246: 247: 248:
249: protected $transferEncoding;
250:
251: 252: 253: 254: 255:
256: protected $_transferEncodingAvailable = [
257: '7bit',
258: '8bit',
259: 'base64',
260: 'binary',
261: 'quoted-printable'
262: ];
263:
264: 265: 266: 267: 268:
269: protected $_appCharset;
270:
271: 272: 273: 274: 275: 276: 277:
278: protected $_attachments = [];
279:
280: 281: 282: 283: 284:
285: protected $_boundary;
286:
287: 288: 289: 290: 291:
292: protected $_priority;
293:
294: 295: 296: 297: 298: 299: 300:
301: protected static $_dsnClassMap = [];
302:
303: 304: 305: 306: 307: 308:
309: protected $_profile = [];
310:
311: 312: 313: 314: 315:
316: protected $_charset8bit = ['UTF-8', 'SHIFT_JIS'];
317:
318: 319: 320: 321: 322:
323: protected $_contentTypeCharset = [
324: 'ISO-2022-JP-MS' => 'ISO-2022-JP'
325: ];
326:
327: 328: 329: 330: 331: 332: 333: 334:
335: protected $_emailPattern = self::EMAIL_PATTERN;
336:
337: 338: 339: 340: 341:
342: public function __construct($config = null)
343: {
344: $this->_appCharset = Configure::read('App.encoding');
345: if ($this->_appCharset !== null) {
346: $this->charset = $this->_appCharset;
347: }
348: $this->_domain = preg_replace('/\:\d+$/', '', env('HTTP_HOST'));
349: if (empty($this->_domain)) {
350: $this->_domain = php_uname('n');
351: }
352:
353: $this->viewBuilder()
354: ->setClassName('Cake\View\View')
355: ->setTemplate('')
356: ->setLayout('default')
357: ->setHelpers(['Html']);
358:
359: if ($config === null) {
360: $config = static::getConfig('default');
361: }
362: if ($config) {
363: $this->setProfile($config);
364: }
365: if (empty($this->headerCharset)) {
366: $this->headerCharset = $this->charset;
367: }
368: }
369:
370: 371: 372: 373: 374:
375: public function __clone()
376: {
377: $this->_viewBuilder = clone $this->viewBuilder();
378: }
379:
380: 381: 382: 383: 384: 385: 386: 387: 388:
389: public function setFrom($email, $name = null)
390: {
391: return $this->_setEmailSingle('_from', $email, $name, 'From requires only 1 email address.');
392: }
393:
394: 395: 396: 397: 398:
399: public function getFrom()
400: {
401: return $this->_from;
402: }
403:
404: 405: 406: 407: 408: 409: 410: 411: 412: 413:
414: public function from($email = null, $name = null)
415: {
416: deprecationWarning('Email::from() is deprecated. Use Email::setFrom() or Email::getFrom() instead.');
417: if ($email === null) {
418: return $this->getFrom();
419: }
420:
421: return $this->setFrom($email, $name);
422: }
423:
424: 425: 426: 427: 428: 429: 430: 431: 432:
433: public function setSender($email, $name = null)
434: {
435: return $this->_setEmailSingle('_sender', $email, $name, 'Sender requires only 1 email address.');
436: }
437:
438: 439: 440: 441: 442:
443: public function getSender()
444: {
445: return $this->_sender;
446: }
447:
448: 449: 450: 451: 452: 453: 454: 455: 456: 457:
458: public function sender($email = null, $name = null)
459: {
460: deprecationWarning('Email::sender() is deprecated. Use Email::setSender() or Email::getSender() instead.');
461:
462: if ($email === null) {
463: return $this->getSender();
464: }
465:
466: return $this->setSender($email, $name);
467: }
468:
469: 470: 471: 472: 473: 474: 475: 476: 477:
478: public function setReplyTo($email, $name = null)
479: {
480: return $this->_setEmailSingle('_replyTo', $email, $name, 'Reply-To requires only 1 email address.');
481: }
482:
483: 484: 485: 486: 487:
488: public function getReplyTo()
489: {
490: return $this->_replyTo;
491: }
492:
493: 494: 495: 496: 497: 498: 499: 500: 501: 502:
503: public function replyTo($email = null, $name = null)
504: {
505: deprecationWarning('Email::replyTo() is deprecated. Use Email::setReplyTo() or Email::getReplyTo() instead.');
506:
507: if ($email === null) {
508: return $this->getReplyTo();
509: }
510:
511: return $this->setReplyTo($email, $name);
512: }
513:
514: 515: 516: 517: 518: 519: 520: 521: 522:
523: public function setReadReceipt($email, $name = null)
524: {
525: return $this->_setEmailSingle('_readReceipt', $email, $name, 'Disposition-Notification-To requires only 1 email address.');
526: }
527:
528: 529: 530: 531: 532:
533: public function getReadReceipt()
534: {
535: return $this->_readReceipt;
536: }
537:
538: 539: 540: 541: 542: 543: 544: 545: 546: 547:
548: public function readReceipt($email = null, $name = null)
549: {
550: deprecationWarning('Email::readReceipt() is deprecated. Use Email::setReadReceipt() or Email::getReadReceipt() instead.');
551:
552: if ($email === null) {
553: return $this->getReadReceipt();
554: }
555:
556: return $this->setReadReceipt($email, $name);
557: }
558:
559: 560: 561: 562: 563: 564: 565: 566: 567:
568: public function setReturnPath($email, $name = null)
569: {
570: return $this->_setEmailSingle('_returnPath', $email, $name, 'Return-Path requires only 1 email address.');
571: }
572:
573: 574: 575: 576: 577:
578: public function getReturnPath()
579: {
580: return $this->_returnPath;
581: }
582:
583: 584: 585: 586: 587: 588: 589: 590: 591: 592:
593: public function returnPath($email = null, $name = null)
594: {
595: deprecationWarning('Email::returnPath() is deprecated. Use Email::setReturnPath() or Email::getReturnPath() instead.');
596: if ($email === null) {
597: return $this->getReturnPath();
598: }
599:
600: return $this->setReturnPath($email, $name);
601: }
602:
603: 604: 605: 606: 607: 608: 609: 610:
611: public function setTo($email, $name = null)
612: {
613: return $this->_setEmail('_to', $email, $name);
614: }
615:
616: 617: 618: 619: 620:
621: public function getTo()
622: {
623: return $this->_to;
624: }
625:
626: 627: 628: 629: 630: 631: 632: 633: 634:
635: public function to($email = null, $name = null)
636: {
637: deprecationWarning('Email::to() is deprecated. Use Email::setTo() or Email::getTo() instead.');
638:
639: if ($email === null) {
640: return $this->getTo();
641: }
642:
643: return $this->setTo($email, $name);
644: }
645:
646: 647: 648: 649: 650: 651: 652: 653:
654: public function addTo($email, $name = null)
655: {
656: return $this->_addEmail('_to', $email, $name);
657: }
658:
659: 660: 661: 662: 663: 664: 665: 666:
667: public function setCc($email, $name = null)
668: {
669: return $this->_setEmail('_cc', $email, $name);
670: }
671:
672: 673: 674: 675: 676:
677: public function getCc()
678: {
679: return $this->_cc;
680: }
681:
682: 683: 684: 685: 686: 687: 688: 689: 690:
691: public function cc($email = null, $name = null)
692: {
693: deprecationWarning('Email::cc() is deprecated. Use Email::setCc() or Email::getCc() instead.');
694:
695: if ($email === null) {
696: return $this->getCc();
697: }
698:
699: return $this->setCc($email, $name);
700: }
701:
702: 703: 704: 705: 706: 707: 708: 709:
710: public function addCc($email, $name = null)
711: {
712: return $this->_addEmail('_cc', $email, $name);
713: }
714:
715: 716: 717: 718: 719: 720: 721: 722:
723: public function setBcc($email, $name = null)
724: {
725: return $this->_setEmail('_bcc', $email, $name);
726: }
727:
728: 729: 730: 731: 732:
733: public function getBcc()
734: {
735: return $this->_bcc;
736: }
737:
738: 739: 740: 741: 742: 743: 744: 745: 746:
747: public function bcc($email = null, $name = null)
748: {
749: deprecationWarning('Email::bcc() is deprecated. Use Email::setBcc() or Email::getBcc() instead.');
750:
751: if ($email === null) {
752: return $this->getBcc();
753: }
754:
755: return $this->setBcc($email, $name);
756: }
757:
758: 759: 760: 761: 762: 763: 764: 765:
766: public function addBcc($email, $name = null)
767: {
768: return $this->_addEmail('_bcc', $email, $name);
769: }
770:
771: 772: 773: 774: 775: 776:
777: public function setCharset($charset)
778: {
779: $this->charset = $charset;
780: if (!$this->headerCharset) {
781: $this->headerCharset = $charset;
782: }
783:
784: return $this;
785: }
786:
787: 788: 789: 790: 791:
792: public function getCharset()
793: {
794: return $this->charset;
795: }
796:
797: 798: 799: 800: 801: 802: 803:
804: public function charset($charset = null)
805: {
806: deprecationWarning('Email::charset() is deprecated. Use Email::setCharset() or Email::getCharset() instead.');
807:
808: if ($charset === null) {
809: return $this->getCharset();
810: }
811: $this->setCharset($charset);
812:
813: return $this->charset;
814: }
815:
816: 817: 818: 819: 820: 821:
822: public function setHeaderCharset($charset)
823: {
824: $this->headerCharset = $charset;
825:
826: return $this;
827: }
828:
829: 830: 831: 832: 833:
834: public function getHeaderCharset()
835: {
836: return $this->headerCharset;
837: }
838:
839: 840: 841: 842: 843: 844: 845:
846: public function headerCharset($charset = null)
847: {
848: deprecationWarning('Email::headerCharset() is deprecated. Use Email::setHeaderCharset() or Email::getHeaderCharset() instead.');
849:
850: if ($charset === null) {
851: return $this->getHeaderCharset();
852: }
853:
854: $this->setHeaderCharset($charset);
855:
856: return $this->headerCharset;
857: }
858:
859: 860: 861: 862: 863: 864:
865: public function setTransferEncoding($encoding)
866: {
867: $encoding = strtolower($encoding);
868: if (!in_array($encoding, $this->_transferEncodingAvailable)) {
869: throw new InvalidArgumentException(
870: sprintf(
871: 'Transfer encoding not available. Can be : %s.',
872: implode(', ', $this->_transferEncodingAvailable)
873: )
874: );
875: }
876: $this->transferEncoding = $encoding;
877:
878: return $this;
879: }
880:
881: 882: 883: 884: 885:
886: public function getTransferEncoding()
887: {
888: return $this->transferEncoding;
889: }
890:
891: 892: 893: 894: 895: 896: 897:
898: public function setEmailPattern($regex)
899: {
900: $this->_emailPattern = $regex;
901:
902: return $this;
903: }
904:
905: 906: 907: 908: 909:
910: public function getEmailPattern()
911: {
912: return $this->_emailPattern;
913: }
914:
915: 916: 917: 918: 919: 920: 921: 922: 923:
924: public function emailPattern($regex = false)
925: {
926: deprecationWarning('Email::emailPattern() is deprecated. Use Email::setEmailPattern() or Email::getEmailPattern() instead.');
927:
928: if ($regex === false) {
929: return $this->getEmailPattern();
930: }
931:
932: return $this->setEmailPattern($regex);
933: }
934:
935: 936: 937: 938: 939: 940: 941: 942: 943: 944:
945: protected function _setEmail($varName, $email, $name)
946: {
947: if (!is_array($email)) {
948: $this->_validateEmail($email, $varName);
949: if ($name === null) {
950: $name = $email;
951: }
952: $this->{$varName} = [$email => $name];
953:
954: return $this;
955: }
956: $list = [];
957: foreach ($email as $key => $value) {
958: if (is_int($key)) {
959: $key = $value;
960: }
961: $this->_validateEmail($key, $varName);
962: $list[$key] = $value;
963: }
964: $this->{$varName} = $list;
965:
966: return $this;
967: }
968:
969: 970: 971: 972: 973: 974: 975: 976:
977: protected function _validateEmail($email, $context)
978: {
979: if ($this->_emailPattern === null) {
980: if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
981: return;
982: }
983: } elseif (preg_match($this->_emailPattern, $email)) {
984: return;
985: }
986:
987: $context = ltrim($context, '_');
988: if ($email == '') {
989: throw new InvalidArgumentException(sprintf('The email set for "%s" is empty.', $context));
990: }
991: throw new InvalidArgumentException(sprintf('Invalid email set for "%s". You passed "%s".', $context, $email));
992: }
993:
994: 995: 996: 997: 998: 999: 1000: 1001: 1002: 1003: 1004:
1005: protected function _setEmailSingle($varName, $email, $name, $throwMessage)
1006: {
1007: if ($email === []) {
1008: $this->{$varName} = $email;
1009:
1010: return $this;
1011: }
1012:
1013: $current = $this->{$varName};
1014: $this->_setEmail($varName, $email, $name);
1015: if (count($this->{$varName}) !== 1) {
1016: $this->{$varName} = $current;
1017: throw new InvalidArgumentException($throwMessage);
1018: }
1019:
1020: return $this;
1021: }
1022:
1023: 1024: 1025: 1026: 1027: 1028: 1029: 1030: 1031: 1032:
1033: protected function _addEmail($varName, $email, $name)
1034: {
1035: if (!is_array($email)) {
1036: $this->_validateEmail($email, $varName);
1037: if ($name === null) {
1038: $name = $email;
1039: }
1040: $this->{$varName}[$email] = $name;
1041:
1042: return $this;
1043: }
1044: $list = [];
1045: foreach ($email as $key => $value) {
1046: if (is_int($key)) {
1047: $key = $value;
1048: }
1049: $this->_validateEmail($key, $varName);
1050: $list[$key] = $value;
1051: }
1052: $this->{$varName} = array_merge($this->{$varName}, $list);
1053:
1054: return $this;
1055: }
1056:
1057: 1058: 1059: 1060: 1061: 1062:
1063: public function setSubject($subject)
1064: {
1065: $this->_subject = $this->_encode((string)$subject);
1066:
1067: return $this;
1068: }
1069:
1070: 1071: 1072: 1073: 1074:
1075: public function getSubject()
1076: {
1077: return $this->_subject;
1078: }
1079:
1080: 1081: 1082: 1083: 1084: 1085: 1086:
1087: public function subject($subject = null)
1088: {
1089: deprecationWarning('Email::subject() is deprecated. Use Email::setSubject() or Email::getSubject() instead.');
1090:
1091: if ($subject === null) {
1092: return $this->getSubject();
1093: }
1094:
1095: return $this->setSubject($subject);
1096: }
1097:
1098: 1099: 1100: 1101: 1102:
1103: public function getOriginalSubject()
1104: {
1105: return $this->_decode($this->_subject);
1106: }
1107:
1108: 1109: 1110: 1111: 1112: 1113:
1114: public function setHeaders(array $headers)
1115: {
1116: $this->_headers = $headers;
1117:
1118: return $this;
1119: }
1120:
1121: 1122: 1123: 1124: 1125: 1126:
1127: public function addHeaders(array $headers)
1128: {
1129: $this->_headers = array_merge($this->_headers, $headers);
1130:
1131: return $this;
1132: }
1133:
1134: 1135: 1136: 1137: 1138: 1139: 1140: 1141: 1142: 1143: 1144: 1145: 1146: 1147: 1148: 1149: 1150:
1151: public function getHeaders(array $include = [])
1152: {
1153: if ($include == array_values($include)) {
1154: $include = array_fill_keys($include, true);
1155: }
1156: $defaults = array_fill_keys(
1157: [
1158: 'from', 'sender', 'replyTo', 'readReceipt', 'returnPath',
1159: 'to', 'cc', 'bcc', 'subject'],
1160: false
1161: );
1162: $include += $defaults;
1163:
1164: $headers = [];
1165: $relation = [
1166: 'from' => 'From',
1167: 'replyTo' => 'Reply-To',
1168: 'readReceipt' => 'Disposition-Notification-To',
1169: 'returnPath' => 'Return-Path'
1170: ];
1171: foreach ($relation as $var => $header) {
1172: if ($include[$var]) {
1173: $var = '_' . $var;
1174: $headers[$header] = current($this->_formatAddress($this->{$var}));
1175: }
1176: }
1177: if ($include['sender']) {
1178: if (key($this->_sender) === key($this->_from)) {
1179: $headers['Sender'] = '';
1180: } else {
1181: $headers['Sender'] = current($this->_formatAddress($this->_sender));
1182: }
1183: }
1184:
1185: foreach (['to', 'cc', 'bcc'] as $var) {
1186: if ($include[$var]) {
1187: $classVar = '_' . $var;
1188: $headers[ucfirst($var)] = implode(', ', $this->_formatAddress($this->{$classVar}));
1189: }
1190: }
1191:
1192: $headers += $this->_headers;
1193: if (!isset($headers['Date'])) {
1194: $headers['Date'] = date(DATE_RFC2822);
1195: }
1196: if ($this->_messageId !== false) {
1197: if ($this->_messageId === true) {
1198: $this->_messageId = '<' . str_replace('-', '', Text::uuid()) . '@' . $this->_domain . '>';
1199: }
1200:
1201: $headers['Message-ID'] = $this->_messageId;
1202: }
1203:
1204: if ($this->_priority) {
1205: $headers['X-Priority'] = $this->_priority;
1206: }
1207:
1208: if ($include['subject']) {
1209: $headers['Subject'] = $this->_subject;
1210: }
1211:
1212: $headers['MIME-Version'] = '1.0';
1213: if ($this->_attachments) {
1214: $headers['Content-Type'] = 'multipart/mixed; boundary="' . $this->_boundary . '"';
1215: } elseif ($this->_emailFormat === 'both') {
1216: $headers['Content-Type'] = 'multipart/alternative; boundary="' . $this->_boundary . '"';
1217: } elseif ($this->_emailFormat === 'text') {
1218: $headers['Content-Type'] = 'text/plain; charset=' . $this->_getContentTypeCharset();
1219: } elseif ($this->_emailFormat === 'html') {
1220: $headers['Content-Type'] = 'text/html; charset=' . $this->_getContentTypeCharset();
1221: }
1222: $headers['Content-Transfer-Encoding'] = $this->_getContentTransferEncoding();
1223:
1224: return $headers;
1225: }
1226:
1227: 1228: 1229: 1230: 1231: 1232: 1233: 1234: 1235: 1236:
1237: protected function _formatAddress($address)
1238: {
1239: $return = [];
1240: foreach ($address as $email => $alias) {
1241: if ($email === $alias) {
1242: $return[] = $email;
1243: } else {
1244: $encoded = $this->_encode($alias);
1245: if ($encoded === $alias && preg_match('/[^a-z0-9 ]/i', $encoded)) {
1246: $encoded = '"' . str_replace('"', '\"', $encoded) . '"';
1247: }
1248: $return[] = sprintf('%s <%s>', $encoded, $email);
1249: }
1250: }
1251:
1252: return $return;
1253: }
1254:
1255: 1256: 1257: 1258: 1259: 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: 1274: 1275: 1276:
1277: public function getTemplate()
1278: {
1279: deprecationWarning(
1280: 'Email::getTemplate() is deprecated. Use $email->viewBuilder()->getTemplate() instead.'
1281: );
1282:
1283: return $this->viewBuilder()->getTemplate();
1284: }
1285:
1286: 1287: 1288: 1289: 1290: 1291: 1292:
1293: public function setLayout($layout)
1294: {
1295: deprecationWarning(
1296: 'Email::setLayout() is deprecated. Use $email->viewBuilder()->setLayout() instead.'
1297: );
1298:
1299: $this->viewBuilder()->setLayout($layout ?: false);
1300:
1301: return $this;
1302: }
1303:
1304: 1305: 1306: 1307: 1308: 1309:
1310: public function getLayout()
1311: {
1312: deprecationWarning(
1313: 'Email::getLayout() is deprecated. Use $email->viewBuilder()->getLayout() instead.'
1314: );
1315:
1316: return $this->viewBuilder()->getLayout();
1317: }
1318:
1319: 1320: 1321: 1322: 1323: 1324: 1325: 1326:
1327: public function template($template = false, $layout = false)
1328: {
1329: deprecationWarning(
1330: 'Email::template() is deprecated. ' .
1331: 'Use $email->viewBuilder()->getTemplate()/setTemplate() ' .
1332: 'and $email->viewBuilder()->getLayout()/setLayout() instead.'
1333: );
1334:
1335: if ($template === false) {
1336: return [
1337: 'template' => $this->getTemplate(),
1338: 'layout' => $this->getLayout()
1339: ];
1340: }
1341: $this->setTemplate($template);
1342: if ($layout !== false) {
1343: $this->setLayout($layout);
1344: }
1345:
1346: return $this;
1347: }
1348:
1349: 1350: 1351: 1352: 1353: 1354:
1355: public function setViewRenderer($viewClass)
1356: {
1357: $this->viewBuilder()->setClassName($viewClass);
1358:
1359: return $this;
1360: }
1361:
1362: 1363: 1364: 1365: 1366:
1367: public function getViewRenderer()
1368: {
1369: return $this->viewBuilder()->getClassName();
1370: }
1371:
1372: 1373: 1374: 1375: 1376: 1377: 1378:
1379: public function viewRender($viewClass = null)
1380: {
1381: deprecationWarning('Email::viewRender() is deprecated. Use Email::setViewRenderer() or Email::getViewRenderer() instead.');
1382:
1383: if ($viewClass === null) {
1384: return $this->getViewRenderer();
1385: }
1386: $this->setViewRenderer($viewClass);
1387:
1388: return $this;
1389: }
1390:
1391: 1392: 1393: 1394: 1395: 1396:
1397: public function setViewVars($viewVars)
1398: {
1399: $this->set((array)$viewVars);
1400:
1401: return $this;
1402: }
1403:
1404: 1405: 1406: 1407: 1408:
1409: public function getViewVars()
1410: {
1411: return $this->viewVars;
1412: }
1413:
1414: 1415: 1416: 1417: 1418: 1419: 1420:
1421: public function viewVars($viewVars = null)
1422: {
1423: deprecationWarning('Email::viewVars() is deprecated. Use Email::setViewVars() or Email::getViewVars() instead.');
1424:
1425: if ($viewVars === null) {
1426: return $this->getViewVars();
1427: }
1428:
1429: return $this->setViewVars($viewVars);
1430: }
1431:
1432: 1433: 1434: 1435: 1436: 1437: 1438:
1439: public function setTheme($theme)
1440: {
1441: deprecationWarning(
1442: 'Email::setTheme() is deprecated. Use $email->viewBuilder()->setTheme() instead.'
1443: );
1444:
1445: $this->viewBuilder()->setTheme($theme);
1446:
1447: return $this;
1448: }
1449:
1450: 1451: 1452: 1453: 1454: 1455:
1456: public function getTheme()
1457: {
1458: deprecationWarning(
1459: 'Email::getTheme() is deprecated. Use $email->viewBuilder()->getTheme() instead.'
1460: );
1461:
1462: return $this->viewBuilder()->getTheme();
1463: }
1464:
1465: 1466: 1467: 1468: 1469: 1470: 1471:
1472: public function theme($theme = null)
1473: {
1474: deprecationWarning(
1475: 'Email::theme() is deprecated. Use $email->viewBuilder()->getTheme()/setTheme() instead.'
1476: );
1477:
1478: if ($theme === null) {
1479: return $this->getTheme();
1480: }
1481:
1482: return $this->setTheme($theme);
1483: }
1484:
1485: 1486: 1487: 1488: 1489: 1490: 1491:
1492: public function setHelpers(array $helpers)
1493: {
1494: deprecationWarning(
1495: 'Email::setHelpers() is deprecated. Use $email->viewBuilder()->setHelpers() instead.'
1496: );
1497:
1498: $this->viewBuilder()->setHelpers($helpers, false);
1499:
1500: return $this;
1501: }
1502:
1503: 1504: 1505: 1506: 1507: 1508:
1509: public function getHelpers()
1510: {
1511: deprecationWarning(
1512: 'Email::getHelpers() is deprecated. Use $email->viewBuilder()->getHelpers() instead.'
1513: );
1514:
1515: return $this->viewBuilder()->getHelpers();
1516: }
1517:
1518: 1519: 1520: 1521: 1522: 1523: 1524:
1525: public function helpers($helpers = null)
1526: {
1527: deprecationWarning(
1528: 'Email::helpers() is deprecated. Use $email->viewBuilder()->getHelpers()/setHelpers() instead.'
1529: );
1530:
1531: if ($helpers === null) {
1532: return $this->getHelpers();
1533: }
1534:
1535: return $this->setHelpers((array)$helpers);
1536: }
1537:
1538: 1539: 1540: 1541: 1542: 1543: 1544:
1545: public function setEmailFormat($format)
1546: {
1547: if (!in_array($format, $this->_emailFormatAvailable)) {
1548: throw new InvalidArgumentException('Format not available.');
1549: }
1550: $this->_emailFormat = $format;
1551:
1552: return $this;
1553: }
1554:
1555: 1556: 1557: 1558: 1559:
1560: public function getEmailFormat()
1561: {
1562: return $this->_emailFormat;
1563: }
1564:
1565: 1566: 1567: 1568: 1569: 1570: 1571: 1572:
1573: public function emailFormat($format = null)
1574: {
1575: deprecationWarning('Email::emailFormat() is deprecated. Use Email::setEmailFormat() or Email::getEmailFormat() instead.');
1576:
1577: if ($format === null) {
1578: return $this->getEmailFormat();
1579: }
1580:
1581: return $this->setEmailFormat($format);
1582: }
1583:
1584: 1585: 1586: 1587: 1588: 1589: 1590: 1591: 1592: 1593: 1594: 1595:
1596: public function setTransport($name)
1597: {
1598: if (is_string($name)) {
1599: $transport = TransportFactory::get($name);
1600: } elseif (is_object($name)) {
1601: $transport = $name;
1602: } else {
1603: throw new InvalidArgumentException(
1604: sprintf('The value passed for the "$name" argument must be either a string, or an object, %s given.', gettype($name))
1605: );
1606: }
1607: if (!method_exists($transport, 'send')) {
1608: throw new LogicException(sprintf('The "%s" do not have send method.', get_class($transport)));
1609: }
1610:
1611: $this->_transport = $transport;
1612:
1613: return $this;
1614: }
1615:
1616: 1617: 1618: 1619: 1620:
1621: public function getTransport()
1622: {
1623: return $this->_transport;
1624: }
1625:
1626: 1627: 1628: 1629: 1630: 1631: 1632: 1633: 1634: 1635: 1636: 1637: 1638:
1639: public function transport($name = null)
1640: {
1641: deprecationWarning('Email::transport() is deprecated. Use Email::setTransport() or Email::getTransport() instead.');
1642:
1643: if ($name === null) {
1644: return $this->getTransport();
1645: }
1646:
1647: return $this->setTransport($name);
1648: }
1649:
1650: 1651: 1652: 1653: 1654: 1655: 1656:
1657: public function setMessageId($message)
1658: {
1659: if (is_bool($message)) {
1660: $this->_messageId = $message;
1661: } else {
1662: if (!preg_match('/^\<.+@.+\>$/', $message)) {
1663: throw new InvalidArgumentException('Invalid format to Message-ID. The text should be something like "<uuid@server.com>"');
1664: }
1665: $this->_messageId = $message;
1666: }
1667:
1668: return $this;
1669: }
1670:
1671: 1672: 1673: 1674: 1675:
1676: public function getMessageId()
1677: {
1678: return $this->_messageId;
1679: }
1680:
1681: 1682: 1683: 1684: 1685: 1686: 1687: 1688:
1689: public function messageId($message = null)
1690: {
1691: deprecationWarning('Email::messageId() is deprecated. Use Email::setMessageId() or Email::getMessageId() instead.');
1692:
1693: if ($message === null) {
1694: return $this->getMessageId();
1695: }
1696:
1697: return $this->setMessageId($message);
1698: }
1699:
1700: 1701: 1702: 1703: 1704: 1705: 1706: 1707:
1708: public function setDomain($domain)
1709: {
1710: $this->_domain = $domain;
1711:
1712: return $this;
1713: }
1714:
1715: 1716: 1717: 1718: 1719:
1720: public function getDomain()
1721: {
1722: return $this->_domain;
1723: }
1724:
1725: 1726: 1727: 1728: 1729: 1730: 1731:
1732: public function domain($domain = null)
1733: {
1734: deprecationWarning('Email::domain() is deprecated. Use Email::setDomain() or Email::getDomain() instead.');
1735:
1736: if ($domain === null) {
1737: return $this->getDomain();
1738: }
1739:
1740: return $this->setDomain($domain);
1741: }
1742:
1743: 1744: 1745: 1746: 1747: 1748: 1749: 1750: 1751: 1752: 1753: 1754: 1755: 1756: 1757: 1758: 1759: 1760: 1761: 1762: 1763: 1764: 1765: 1766: 1767: 1768: 1769: 1770: 1771: 1772: 1773: 1774: 1775: 1776: 1777: 1778: 1779: 1780: 1781: 1782: 1783: 1784: 1785: 1786: 1787: 1788: 1789: 1790: 1791:
1792: public function setAttachments($attachments)
1793: {
1794: $attach = [];
1795: foreach ((array)$attachments as $name => $fileInfo) {
1796: if (!is_array($fileInfo)) {
1797: $fileInfo = ['file' => $fileInfo];
1798: }
1799: if (!isset($fileInfo['file'])) {
1800: if (!isset($fileInfo['data'])) {
1801: throw new InvalidArgumentException('No file or data specified.');
1802: }
1803: if (is_int($name)) {
1804: throw new InvalidArgumentException('No filename specified.');
1805: }
1806: $fileInfo['data'] = chunk_split(base64_encode($fileInfo['data']), 76, "\r\n");
1807: } else {
1808: $fileName = $fileInfo['file'];
1809: $fileInfo['file'] = realpath($fileInfo['file']);
1810: if ($fileInfo['file'] === false || !file_exists($fileInfo['file'])) {
1811: throw new InvalidArgumentException(sprintf('File not found: "%s"', $fileName));
1812: }
1813: if (is_int($name)) {
1814: $name = basename($fileInfo['file']);
1815: }
1816: }
1817: if (!isset($fileInfo['mimetype']) && isset($fileInfo['file']) && function_exists('mime_content_type')) {
1818: $fileInfo['mimetype'] = mime_content_type($fileInfo['file']);
1819: }
1820: if (!isset($fileInfo['mimetype'])) {
1821: $fileInfo['mimetype'] = 'application/octet-stream';
1822: }
1823: $attach[$name] = $fileInfo;
1824: }
1825: $this->_attachments = $attach;
1826:
1827: return $this;
1828: }
1829:
1830: 1831: 1832: 1833: 1834:
1835: public function getAttachments()
1836: {
1837: return $this->_attachments;
1838: }
1839:
1840: 1841: 1842: 1843: 1844: 1845: 1846: 1847: 1848: 1849: 1850: 1851: 1852: 1853: 1854: 1855: 1856: 1857: 1858: 1859: 1860: 1861: 1862: 1863: 1864: 1865: 1866: 1867: 1868: 1869: 1870: 1871: 1872: 1873: 1874: 1875: 1876: 1877: 1878: 1879: 1880: 1881: 1882: 1883: 1884: 1885: 1886: 1887: 1888: 1889:
1890: public function attachments($attachments = null)
1891: {
1892: deprecationWarning('Email::attachments() is deprecated. Use Email::setAttachments() or Email::getAttachments() instead.');
1893:
1894: if ($attachments === null) {
1895: return $this->getAttachments();
1896: }
1897:
1898: return $this->setAttachments($attachments);
1899: }
1900:
1901: 1902: 1903: 1904: 1905: 1906: 1907: 1908:
1909: public function addAttachments($attachments)
1910: {
1911: $current = $this->_attachments;
1912: $this->setAttachments($attachments);
1913: $this->_attachments = array_merge($current, $this->_attachments);
1914:
1915: return $this;
1916: }
1917:
1918: 1919: 1920: 1921: 1922: 1923:
1924: public function message($type = null)
1925: {
1926: switch ($type) {
1927: case static::MESSAGE_HTML:
1928: return $this->_htmlMessage;
1929: case static::MESSAGE_TEXT:
1930: return $this->_textMessage;
1931: }
1932:
1933: return $this->_message;
1934: }
1935:
1936: 1937: 1938: 1939: 1940: 1941:
1942: public function setPriority($priority)
1943: {
1944: $this->_priority = $priority;
1945:
1946: return $this;
1947: }
1948:
1949: 1950: 1951: 1952: 1953:
1954: public function getPriority()
1955: {
1956: return $this->_priority;
1957: }
1958:
1959: 1960: 1961: 1962: 1963: 1964: 1965: 1966: 1967: 1968: 1969: 1970: 1971: 1972: 1973: 1974: 1975: 1976: 1977: 1978: 1979:
1980: public static function setConfigTransport($key, $config = null)
1981: {
1982: deprecationWarning('Email::setConfigTransport() is deprecated. Use TransportFactory::setConfig() instead.');
1983:
1984: TransportFactory::setConfig($key, $config);
1985: }
1986:
1987: 1988: 1989: 1990: 1991: 1992: 1993:
1994: public static function getConfigTransport($key)
1995: {
1996: deprecationWarning('Email::getConfigTransport() is deprecated. Use TransportFactory::getConfig() instead.');
1997:
1998: return TransportFactory::getConfig($key);
1999: }
2000:
2001: 2002: 2003: 2004: 2005: 2006: 2007: 2008: 2009: 2010: 2011: 2012: 2013: 2014: 2015: 2016: 2017: 2018: 2019: 2020: 2021: 2022:
2023: public static function configTransport($key, $config = null)
2024: {
2025: deprecationWarning('Email::configTransport() is deprecated. Use TransportFactory::setConfig() or TransportFactory::getConfig() instead.');
2026:
2027: if ($config === null && is_string($key)) {
2028: return TransportFactory::getConfig($key);
2029: }
2030: if ($config === null && is_array($key)) {
2031: TransportFactory::setConfig($key);
2032:
2033: return null;
2034: }
2035:
2036: TransportFactory::setConfig($key, $config);
2037: }
2038:
2039: 2040: 2041: 2042: 2043: 2044:
2045: public static function configuredTransport()
2046: {
2047: deprecationWarning('Email::configuredTransport() is deprecated. Use TransportFactory::configured().');
2048:
2049: return TransportFactory::configured();
2050: }
2051:
2052: 2053: 2054: 2055: 2056: 2057: 2058:
2059: public static function dropTransport($key)
2060: {
2061: deprecationWarning('Email::dropTransport() is deprecated. Use TransportFactory::drop().');
2062:
2063: TransportFactory::drop($key);
2064: }
2065:
2066: 2067: 2068: 2069: 2070: 2071: 2072:
2073: public function setProfile($config)
2074: {
2075: if (!is_array($config)) {
2076: $config = (string)$config;
2077: }
2078: $this->_applyConfig($config);
2079:
2080: return $this;
2081: }
2082:
2083: 2084: 2085: 2086: 2087:
2088: public function getProfile()
2089: {
2090: return $this->_profile;
2091: }
2092:
2093: 2094: 2095: 2096: 2097: 2098: 2099: 2100:
2101: public function profile($config = null)
2102: {
2103: deprecationWarning('Email::profile() is deprecated. Use Email::setProfile() or Email::getProfile() instead.');
2104:
2105: if ($config === null) {
2106: return $this->getProfile();
2107: }
2108:
2109: return $this->setProfile($config);
2110: }
2111:
2112: 2113: 2114: 2115: 2116: 2117: 2118:
2119: public function send($content = null)
2120: {
2121: if (empty($this->_from)) {
2122: throw new BadMethodCallException('From is not specified.');
2123: }
2124: if (empty($this->_to) && empty($this->_cc) && empty($this->_bcc)) {
2125: throw new BadMethodCallException('You need specify one destination on to, cc or bcc.');
2126: }
2127:
2128: if (is_array($content)) {
2129: $content = implode("\n", $content) . "\n";
2130: }
2131:
2132: $this->_message = $this->_render($this->_wrap($content));
2133:
2134: $transport = $this->getTransport();
2135: if (!$transport) {
2136: $msg = 'Cannot send email, transport was not defined. Did you call transport() or define ' .
2137: ' a transport in the set profile?';
2138: throw new BadMethodCallException($msg);
2139: }
2140: $contents = $transport->send($this);
2141: $this->_logDelivery($contents);
2142:
2143: return $contents;
2144: }
2145:
2146: 2147: 2148: 2149: 2150: 2151:
2152: protected function _logDelivery($contents)
2153: {
2154: if (empty($this->_profile['log'])) {
2155: return;
2156: }
2157: $config = [
2158: 'level' => 'debug',
2159: 'scope' => 'email'
2160: ];
2161: if ($this->_profile['log'] !== true) {
2162: if (!is_array($this->_profile['log'])) {
2163: $this->_profile['log'] = ['level' => $this->_profile['log']];
2164: }
2165: $config = $this->_profile['log'] + $config;
2166: }
2167: Log::write(
2168: $config['level'],
2169: PHP_EOL . $this->flatten($contents['headers']) . PHP_EOL . PHP_EOL . $this->flatten($contents['message']),
2170: $config['scope']
2171: );
2172: }
2173:
2174: 2175: 2176: 2177: 2178: 2179:
2180: protected function flatten($value)
2181: {
2182: return is_array($value) ? implode(';', $value) : (string)$value;
2183: }
2184:
2185: 2186: 2187: 2188: 2189: 2190: 2191: 2192: 2193: 2194: 2195:
2196: public static function deliver($to = null, $subject = null, $message = null, $config = 'default', $send = true)
2197: {
2198: $class = __CLASS__;
2199:
2200: if (is_array($config) && !isset($config['transport'])) {
2201: $config['transport'] = 'default';
2202: }
2203:
2204: $instance = new $class($config);
2205: if ($to !== null) {
2206: $instance->setTo($to);
2207: }
2208: if ($subject !== null) {
2209: $instance->setSubject($subject);
2210: }
2211: if (is_array($message)) {
2212: $instance->setViewVars($message);
2213: $message = null;
2214: } elseif ($message === null && array_key_exists('message', $config = $instance->getProfile())) {
2215: $message = $config['message'];
2216: }
2217:
2218: if ($send === true) {
2219: $instance->send($message);
2220: }
2221:
2222: return $instance;
2223: }
2224:
2225: 2226: 2227: 2228: 2229: 2230: 2231:
2232: protected function _applyConfig($config)
2233: {
2234: if (is_string($config)) {
2235: $name = $config;
2236: $config = static::getConfig($name);
2237: if (empty($config)) {
2238: throw new InvalidArgumentException(sprintf('Unknown email configuration "%s".', $name));
2239: }
2240: unset($name);
2241: }
2242:
2243: $this->_profile = array_merge($this->_profile, $config);
2244:
2245: $simpleMethods = [
2246: 'from', 'sender', 'to', 'replyTo', 'readReceipt', 'returnPath',
2247: 'cc', 'bcc', 'messageId', 'domain', 'subject', 'attachments',
2248: 'transport', 'emailFormat', 'emailPattern', 'charset', 'headerCharset'
2249: ];
2250: foreach ($simpleMethods as $method) {
2251: if (isset($config[$method])) {
2252: $this->{'set' . ucfirst($method)}($config[$method]);
2253: }
2254: }
2255:
2256: if (empty($this->headerCharset)) {
2257: $this->headerCharset = $this->charset;
2258: }
2259: if (isset($config['headers'])) {
2260: $this->setHeaders($config['headers']);
2261: }
2262:
2263: $viewBuilderMethods = [
2264: 'template', 'layout', 'theme'
2265: ];
2266: foreach ($viewBuilderMethods as $method) {
2267: if (array_key_exists($method, $config)) {
2268: $this->viewBuilder()->{'set' . ucfirst($method)}($config[$method]);
2269: }
2270: }
2271:
2272: if (array_key_exists('helpers', $config)) {
2273: $this->viewBuilder()->setHelpers($config['helpers'], false);
2274: }
2275: if (array_key_exists('viewRender', $config)) {
2276: $this->viewBuilder()->setClassName($config['viewRender']);
2277: }
2278: if (array_key_exists('viewVars', $config)) {
2279: $this->set($config['viewVars']);
2280: }
2281: }
2282:
2283: 2284: 2285: 2286: 2287:
2288: public function reset()
2289: {
2290: $this->_to = [];
2291: $this->_from = [];
2292: $this->_sender = [];
2293: $this->_replyTo = [];
2294: $this->_readReceipt = [];
2295: $this->_returnPath = [];
2296: $this->_cc = [];
2297: $this->_bcc = [];
2298: $this->_messageId = true;
2299: $this->_subject = '';
2300: $this->_headers = [];
2301: $this->_textMessage = '';
2302: $this->_htmlMessage = '';
2303: $this->_message = [];
2304: $this->_emailFormat = 'text';
2305: $this->_transport = null;
2306: $this->_priority = null;
2307: $this->charset = 'utf-8';
2308: $this->headerCharset = null;
2309: $this->transferEncoding = null;
2310: $this->_attachments = [];
2311: $this->_profile = [];
2312: $this->_emailPattern = self::EMAIL_PATTERN;
2313:
2314: $this->viewBuilder()->setLayout('default');
2315: $this->viewBuilder()->setTemplate('');
2316: $this->viewBuilder()->setClassName('Cake\View\View');
2317: $this->viewVars = [];
2318: $this->viewBuilder()->setTheme(false);
2319: $this->viewBuilder()->setHelpers(['Html'], false);
2320:
2321: return $this;
2322: }
2323:
2324: 2325: 2326: 2327: 2328: 2329:
2330: protected function _encode($text)
2331: {
2332: $restore = mb_internal_encoding();
2333: mb_internal_encoding($this->_appCharset);
2334: if (empty($this->headerCharset)) {
2335: $this->headerCharset = $this->charset;
2336: }
2337: $return = mb_encode_mimeheader($text, $this->headerCharset, 'B');
2338: mb_internal_encoding($restore);
2339:
2340: return $return;
2341: }
2342:
2343: 2344: 2345: 2346: 2347: 2348:
2349: protected function _decode($text)
2350: {
2351: $restore = mb_internal_encoding();
2352: mb_internal_encoding($this->_appCharset);
2353: $return = mb_decode_mimeheader($text);
2354: mb_internal_encoding($restore);
2355:
2356: return $return;
2357: }
2358:
2359: 2360: 2361: 2362: 2363: 2364: 2365: 2366:
2367: protected function _encodeString($text, $charset)
2368: {
2369: if ($this->_appCharset === $charset) {
2370: return $text;
2371: }
2372:
2373: return mb_convert_encoding($text, $charset, $this->_appCharset);
2374: }
2375:
2376: 2377: 2378: 2379: 2380: 2381: 2382:
2383: protected function _wrap($message, $wrapLength = Email::LINE_LENGTH_MUST)
2384: {
2385: if (strlen($message) === 0) {
2386: return [''];
2387: }
2388: $message = str_replace(["\r\n", "\r"], "\n", $message);
2389: $lines = explode("\n", $message);
2390: $formatted = [];
2391: $cut = ($wrapLength == Email::LINE_LENGTH_MUST);
2392:
2393: foreach ($lines as $line) {
2394: if (empty($line) && $line !== '0') {
2395: $formatted[] = '';
2396: continue;
2397: }
2398: if (strlen($line) < $wrapLength) {
2399: $formatted[] = $line;
2400: continue;
2401: }
2402: if (!preg_match('/<[a-z]+.*>/i', $line)) {
2403: $formatted = array_merge(
2404: $formatted,
2405: explode("\n", Text::wordWrap($line, $wrapLength, "\n", $cut))
2406: );
2407: continue;
2408: }
2409:
2410: $tagOpen = false;
2411: $tmpLine = $tag = '';
2412: $tmpLineLength = 0;
2413: for ($i = 0, $count = strlen($line); $i < $count; $i++) {
2414: $char = $line[$i];
2415: if ($tagOpen) {
2416: $tag .= $char;
2417: if ($char === '>') {
2418: $tagLength = strlen($tag);
2419: if ($tagLength + $tmpLineLength < $wrapLength) {
2420: $tmpLine .= $tag;
2421: $tmpLineLength += $tagLength;
2422: } else {
2423: if ($tmpLineLength > 0) {
2424: $formatted = array_merge(
2425: $formatted,
2426: explode("\n", Text::wordWrap(trim($tmpLine), $wrapLength, "\n", $cut))
2427: );
2428: $tmpLine = '';
2429: $tmpLineLength = 0;
2430: }
2431: if ($tagLength > $wrapLength) {
2432: $formatted[] = $tag;
2433: } else {
2434: $tmpLine = $tag;
2435: $tmpLineLength = $tagLength;
2436: }
2437: }
2438: $tag = '';
2439: $tagOpen = false;
2440: }
2441: continue;
2442: }
2443: if ($char === '<') {
2444: $tagOpen = true;
2445: $tag = '<';
2446: continue;
2447: }
2448: if ($char === ' ' && $tmpLineLength >= $wrapLength) {
2449: $formatted[] = $tmpLine;
2450: $tmpLineLength = 0;
2451: continue;
2452: }
2453: $tmpLine .= $char;
2454: $tmpLineLength++;
2455: if ($tmpLineLength === $wrapLength) {
2456: $nextChar = $line[$i + 1];
2457: if ($nextChar === ' ' || $nextChar === '<') {
2458: $formatted[] = trim($tmpLine);
2459: $tmpLine = '';
2460: $tmpLineLength = 0;
2461: if ($nextChar === ' ') {
2462: $i++;
2463: }
2464: } else {
2465: $lastSpace = strrpos($tmpLine, ' ');
2466: if ($lastSpace === false) {
2467: continue;
2468: }
2469: $formatted[] = trim(substr($tmpLine, 0, $lastSpace));
2470: $tmpLine = substr($tmpLine, $lastSpace + 1);
2471:
2472: $tmpLineLength = strlen($tmpLine);
2473: }
2474: }
2475: }
2476: if (!empty($tmpLine)) {
2477: $formatted[] = $tmpLine;
2478: }
2479: }
2480: $formatted[] = '';
2481:
2482: return $formatted;
2483: }
2484:
2485: 2486: 2487: 2488: 2489:
2490: protected function _createBoundary()
2491: {
2492: if ($this->_attachments || $this->_emailFormat === 'both') {
2493: $this->_boundary = md5(Security::randomBytes(16));
2494: }
2495: }
2496:
2497: 2498: 2499: 2500: 2501: 2502:
2503: protected function _attachFiles($boundary = null)
2504: {
2505: if ($boundary === null) {
2506: $boundary = $this->_boundary;
2507: }
2508:
2509: $msg = [];
2510: foreach ($this->_attachments as $filename => $fileInfo) {
2511: if (!empty($fileInfo['contentId'])) {
2512: continue;
2513: }
2514: $data = isset($fileInfo['data']) ? $fileInfo['data'] : $this->_readFile($fileInfo['file']);
2515: $hasDisposition = (
2516: !isset($fileInfo['contentDisposition']) ||
2517: $fileInfo['contentDisposition']
2518: );
2519: $part = new FormDataPart(false, $data, false);
2520:
2521: if ($hasDisposition) {
2522: $part->disposition('attachment');
2523: $part->filename($filename);
2524: }
2525: $part->transferEncoding('base64');
2526: $part->type($fileInfo['mimetype']);
2527:
2528: $msg[] = '--' . $boundary;
2529: $msg[] = (string)$part;
2530: $msg[] = '';
2531: }
2532:
2533: return $msg;
2534: }
2535:
2536: 2537: 2538: 2539: 2540: 2541:
2542: protected function _readFile($path)
2543: {
2544: $File = new File($path);
2545:
2546: return chunk_split(base64_encode($File->read()));
2547: }
2548:
2549: 2550: 2551: 2552: 2553: 2554:
2555: protected function _attachInlineFiles($boundary = null)
2556: {
2557: if ($boundary === null) {
2558: $boundary = $this->_boundary;
2559: }
2560:
2561: $msg = [];
2562: foreach ($this->_attachments as $filename => $fileInfo) {
2563: if (empty($fileInfo['contentId'])) {
2564: continue;
2565: }
2566: $data = isset($fileInfo['data']) ? $fileInfo['data'] : $this->_readFile($fileInfo['file']);
2567:
2568: $msg[] = '--' . $boundary;
2569: $part = new FormDataPart(false, $data, 'inline');
2570: $part->type($fileInfo['mimetype']);
2571: $part->transferEncoding('base64');
2572: $part->contentId($fileInfo['contentId']);
2573: $part->filename($filename);
2574: $msg[] = (string)$part;
2575: $msg[] = '';
2576: }
2577:
2578: return $msg;
2579: }
2580:
2581: 2582: 2583: 2584: 2585: 2586:
2587: protected function _render($content)
2588: {
2589: $this->_textMessage = $this->_htmlMessage = '';
2590:
2591: $content = implode("\n", $content);
2592: $rendered = $this->_renderTemplates($content);
2593:
2594: $this->_createBoundary();
2595: $msg = [];
2596:
2597: $contentIds = array_filter((array)Hash::extract($this->_attachments, '{s}.contentId'));
2598: $hasInlineAttachments = count($contentIds) > 0;
2599: $hasAttachments = !empty($this->_attachments);
2600: $hasMultipleTypes = count($rendered) > 1;
2601: $multiPart = ($hasAttachments || $hasMultipleTypes);
2602:
2603: $boundary = $relBoundary = $textBoundary = $this->_boundary;
2604:
2605: if ($hasInlineAttachments) {
2606: $msg[] = '--' . $boundary;
2607: $msg[] = 'Content-Type: multipart/related; boundary="rel-' . $boundary . '"';
2608: $msg[] = '';
2609: $relBoundary = $textBoundary = 'rel-' . $boundary;
2610: }
2611:
2612: if ($hasMultipleTypes && $hasAttachments) {
2613: $msg[] = '--' . $relBoundary;
2614: $msg[] = 'Content-Type: multipart/alternative; boundary="alt-' . $boundary . '"';
2615: $msg[] = '';
2616: $textBoundary = 'alt-' . $boundary;
2617: }
2618:
2619: if (isset($rendered['text'])) {
2620: if ($multiPart) {
2621: $msg[] = '--' . $textBoundary;
2622: $msg[] = 'Content-Type: text/plain; charset=' . $this->_getContentTypeCharset();
2623: $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding();
2624: $msg[] = '';
2625: }
2626: $this->_textMessage = $rendered['text'];
2627: $content = explode("\n", $this->_textMessage);
2628: $msg = array_merge($msg, $content);
2629: $msg[] = '';
2630: }
2631:
2632: if (isset($rendered['html'])) {
2633: if ($multiPart) {
2634: $msg[] = '--' . $textBoundary;
2635: $msg[] = 'Content-Type: text/html; charset=' . $this->_getContentTypeCharset();
2636: $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding();
2637: $msg[] = '';
2638: }
2639: $this->_htmlMessage = $rendered['html'];
2640: $content = explode("\n", $this->_htmlMessage);
2641: $msg = array_merge($msg, $content);
2642: $msg[] = '';
2643: }
2644:
2645: if ($textBoundary !== $relBoundary) {
2646: $msg[] = '--' . $textBoundary . '--';
2647: $msg[] = '';
2648: }
2649:
2650: if ($hasInlineAttachments) {
2651: $attachments = $this->_attachInlineFiles($relBoundary);
2652: $msg = array_merge($msg, $attachments);
2653: $msg[] = '';
2654: $msg[] = '--' . $relBoundary . '--';
2655: $msg[] = '';
2656: }
2657:
2658: if ($hasAttachments) {
2659: $attachments = $this->_attachFiles($boundary);
2660: $msg = array_merge($msg, $attachments);
2661: }
2662: if ($hasAttachments || $hasMultipleTypes) {
2663: $msg[] = '';
2664: $msg[] = '--' . $boundary . '--';
2665: $msg[] = '';
2666: }
2667:
2668: return $msg;
2669: }
2670:
2671: 2672: 2673: 2674: 2675:
2676: protected function _getTypes()
2677: {
2678: $types = [$this->_emailFormat];
2679: if ($this->_emailFormat === 'both') {
2680: $types = ['html', 'text'];
2681: }
2682:
2683: return $types;
2684: }
2685:
2686: 2687: 2688: 2689: 2690: 2691: 2692: 2693:
2694: protected function _renderTemplates($content)
2695: {
2696: $types = $this->_getTypes();
2697: $rendered = [];
2698: $template = $this->viewBuilder()->getTemplate();
2699: if (empty($template)) {
2700: foreach ($types as $type) {
2701: $rendered[$type] = $this->_encodeString($content, $this->charset);
2702: }
2703:
2704: return $rendered;
2705: }
2706:
2707: $View = $this->createView();
2708:
2709: list($templatePlugin) = pluginSplit($View->getTemplate());
2710: list($layoutPlugin) = pluginSplit($View->getLayout());
2711: if ($templatePlugin) {
2712: $View->setPlugin($templatePlugin);
2713: } elseif ($layoutPlugin) {
2714: $View->setPlugin($layoutPlugin);
2715: }
2716:
2717: if ($View->get('content') === null) {
2718: $View->set('content', $content);
2719: }
2720:
2721: foreach ($types as $type) {
2722: $View->hasRendered = false;
2723: $View->setTemplatePath('Email' . DIRECTORY_SEPARATOR . $type);
2724: $View->setLayoutPath('Email' . DIRECTORY_SEPARATOR . $type);
2725:
2726: $render = $View->render();
2727: $render = str_replace(["\r\n", "\r"], "\n", $render);
2728: $rendered[$type] = $this->_encodeString($render, $this->charset);
2729: }
2730:
2731: foreach ($rendered as $type => $content) {
2732: $rendered[$type] = $this->_wrap($content);
2733: $rendered[$type] = implode("\n", $rendered[$type]);
2734: $rendered[$type] = rtrim($rendered[$type], "\n");
2735: }
2736:
2737: return $rendered;
2738: }
2739:
2740: 2741: 2742: 2743: 2744: 2745:
2746: protected function _getContentTransferEncoding()
2747: {
2748: if ($this->transferEncoding) {
2749: return $this->transferEncoding;
2750: }
2751:
2752: $charset = strtoupper($this->charset);
2753: if (in_array($charset, $this->_charset8bit)) {
2754: return '8bit';
2755: }
2756:
2757: return '7bit';
2758: }
2759:
2760: 2761: 2762: 2763: 2764: 2765: 2766: 2767:
2768: protected function _getContentTypeCharset()
2769: {
2770: $charset = strtoupper($this->charset);
2771: if (array_key_exists($charset, $this->_contentTypeCharset)) {
2772: return strtoupper($this->_contentTypeCharset[$charset]);
2773: }
2774:
2775: return strtoupper($this->charset);
2776: }
2777:
2778: 2779: 2780: 2781: 2782: 2783: 2784: 2785: 2786: 2787: 2788: 2789: 2790: 2791:
2792: public function jsonSerialize()
2793: {
2794: $properties = [
2795: '_to', '_from', '_sender', '_replyTo', '_cc', '_bcc', '_subject',
2796: '_returnPath', '_readReceipt', '_emailFormat', '_emailPattern', '_domain',
2797: '_attachments', '_messageId', '_headers', '_appCharset', 'viewVars', 'charset', 'headerCharset'
2798: ];
2799:
2800: $array = ['viewConfig' => $this->viewBuilder()->jsonSerialize()];
2801:
2802: foreach ($properties as $property) {
2803: $array[$property] = $this->{$property};
2804: }
2805:
2806: array_walk($array['_attachments'], function (&$item, $key) {
2807: if (!empty($item['file'])) {
2808: $item['data'] = $this->_readFile($item['file']);
2809: unset($item['file']);
2810: }
2811: });
2812:
2813: array_walk_recursive($array['viewVars'], [$this, '_checkViewVars']);
2814:
2815: return array_filter($array, function ($i) {
2816: return !is_array($i) && strlen($i) || !empty($i);
2817: });
2818: }
2819:
2820: 2821: 2822: 2823: 2824: 2825: 2826:
2827: protected function _checkViewVars(&$item, $key)
2828: {
2829: if ($item instanceof Exception) {
2830: $item = (string)$item;
2831: }
2832:
2833: if (is_resource($item) ||
2834: $item instanceof Closure ||
2835: $item instanceof PDO
2836: ) {
2837: throw new RuntimeException(sprintf(
2838: 'Failed serializing the `%s` %s in the `%s` view var',
2839: is_resource($item) ? get_resource_type($item) : get_class($item),
2840: is_resource($item) ? 'resource' : 'object',
2841: $key
2842: ));
2843: }
2844: }
2845:
2846: 2847: 2848: 2849: 2850: 2851:
2852: public function createFromArray($config)
2853: {
2854: if (isset($config['viewConfig'])) {
2855: $this->viewBuilder()->createFromArray($config['viewConfig']);
2856: unset($config['viewConfig']);
2857: }
2858:
2859: foreach ($config as $property => $value) {
2860: $this->{$property} = $value;
2861: }
2862:
2863: return $this;
2864: }
2865:
2866: 2867: 2868: 2869: 2870:
2871: public function serialize()
2872: {
2873: $array = $this->jsonSerialize();
2874: array_walk_recursive($array, function (&$item, $key) {
2875: if ($item instanceof SimpleXMLElement) {
2876: $item = json_decode(json_encode((array)$item), true);
2877: }
2878: });
2879:
2880: return serialize($array);
2881: }
2882:
2883: 2884: 2885: 2886: 2887: 2888:
2889: public function unserialize($data)
2890: {
2891: return $this->createFromArray(unserialize($data));
2892: }
2893: }
2894: