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