1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
16:
17: App::uses('Multibyte', 'I18n');
18: App::uses('AbstractTransport', 'Network/Email');
19: App::uses('File', 'Utility');
20: App::uses('CakeText', 'Utility');
21: App::uses('View', 'View');
22:
23: 24: 25: 26: 27: 28: 29: 30:
31: class CakeEmail {
32:
33: 34: 35: 36: 37:
38: const EMAIL_CLIENT = 'CakePHP Email';
39:
40: 41: 42: 43: 44:
45: const LINE_LENGTH_SHOULD = 78;
46:
47: 48: 49: 50: 51:
52: const LINE_LENGTH_MUST = 998;
53:
54: 55: 56: 57: 58:
59: const MESSAGE_HTML = 'html';
60:
61: 62: 63: 64: 65:
66: const MESSAGE_TEXT = 'text';
67:
68: 69: 70: 71: 72:
73: const EMAIL_PATTERN = '/^((?:[\p{L}0-9.!#$%&\'*+\/=?^_`{|}~-]+)*@[\p{L}0-9-_.]+)$/ui';
74:
75: 76: 77: 78: 79:
80: protected $_to = array();
81:
82: 83: 84: 85: 86:
87: protected $_from = array();
88:
89: 90: 91: 92: 93:
94: protected $_sender = array();
95:
96: 97: 98: 99: 100:
101: protected $_replyTo = array();
102:
103: 104: 105: 106: 107:
108: protected $_readReceipt = array();
109:
110: 111: 112: 113: 114: 115: 116: 117:
118: protected $_returnPath = array();
119:
120: 121: 122: 123: 124: 125: 126: 127:
128: protected $_cc = array();
129:
130: 131: 132: 133: 134: 135: 136: 137:
138: protected $_bcc = array();
139:
140: 141: 142: 143: 144:
145: protected $_messageId = true;
146:
147: 148: 149: 150: 151: 152:
153: protected $_domain = null;
154:
155: 156: 157: 158: 159:
160: protected $_subject = '';
161:
162: 163: 164: 165: 166: 167:
168: protected $_headers = array();
169:
170: 171: 172: 173: 174:
175: protected $_layout = 'default';
176:
177: 178: 179: 180: 181:
182: protected $_template = '';
183:
184: 185: 186: 187: 188:
189: protected $_viewRender = 'View';
190:
191: 192: 193: 194: 195:
196: protected $_viewVars = array();
197:
198: 199: 200: 201: 202:
203: protected $_theme = null;
204:
205: 206: 207: 208: 209:
210: protected $_helpers = array('Html');
211:
212: 213: 214: 215: 216:
217: protected $_textMessage = '';
218:
219: 220: 221: 222: 223:
224: protected $_htmlMessage = '';
225:
226: 227: 228: 229: 230:
231: protected $_message = array();
232:
233: 234: 235: 236: 237:
238: protected $_emailFormatAvailable = array('text', 'html', 'both');
239:
240: 241: 242: 243: 244:
245: protected $_emailFormat = 'text';
246:
247: 248: 249: 250: 251:
252: protected $_transportName = 'Mail';
253:
254: 255: 256: 257: 258:
259: protected $_transportClass = null;
260:
261: 262: 263: 264: 265:
266: public $charset = 'utf-8';
267:
268: 269: 270: 271: 272: 273:
274: public $headerCharset = null;
275:
276: 277: 278: 279: 280:
281: protected $_appCharset = null;
282:
283: 284: 285: 286: 287: 288: 289:
290: protected $_attachments = array();
291:
292: 293: 294: 295: 296:
297: protected $_boundary = null;
298:
299: 300: 301: 302: 303:
304: protected $_config = array();
305:
306: 307: 308: 309: 310:
311: protected $_charset8bit = array('UTF-8', 'SHIFT_JIS');
312:
313: 314: 315: 316: 317:
318: protected $_contentTypeCharset = array(
319: 'ISO-2022-JP-MS' => 'ISO-2022-JP'
320: );
321:
322: 323: 324: 325: 326: 327: 328: 329:
330: protected $_emailPattern = self::EMAIL_PATTERN;
331:
332: 333: 334: 335: 336:
337: protected $_configClass = 'EmailConfig';
338:
339: 340: 341: 342: 343:
344: protected $_configInstance;
345:
346: 347: 348: 349: 350:
351: public function __construct($config = null) {
352: $this->_appCharset = Configure::read('App.encoding');
353: if ($this->_appCharset !== null) {
354: $this->charset = $this->_appCharset;
355: }
356: $this->_domain = preg_replace('/\:\d+$/', '', env('HTTP_HOST'));
357: if (empty($this->_domain)) {
358: $this->_domain = php_uname('n');
359: }
360:
361: if ($config) {
362: $this->config($config);
363: } elseif (config('email') && class_exists($this->_configClass)) {
364: $this->_configInstance = new $this->_configClass();
365: if (isset($this->_configInstance->default)) {
366: $this->config('default');
367: }
368: }
369: if (empty($this->headerCharset)) {
370: $this->headerCharset = $this->charset;
371: }
372: }
373:
374: 375: 376: 377: 378: 379: 380: 381: 382:
383: public function from($email = null, $name = null) {
384: if ($email === null) {
385: return $this->_from;
386: }
387: return $this->_setEmailSingle('_from', $email, $name, __d('cake_dev', 'From requires only 1 email address.'));
388: }
389:
390: 391: 392: 393: 394: 395: 396: 397: 398:
399: public function sender($email = null, $name = null) {
400: if ($email === null) {
401: return $this->_sender;
402: }
403: return $this->_setEmailSingle('_sender', $email, $name, __d('cake_dev', 'Sender requires only 1 email address.'));
404: }
405:
406: 407: 408: 409: 410: 411: 412: 413: 414:
415: public function replyTo($email = null, $name = null) {
416: if ($email === null) {
417: return $this->_replyTo;
418: }
419: return $this->_setEmailSingle('_replyTo', $email, $name, __d('cake_dev', 'Reply-To requires only 1 email address.'));
420: }
421:
422: 423: 424: 425: 426: 427: 428: 429: 430:
431: public function readReceipt($email = null, $name = null) {
432: if ($email === null) {
433: return $this->_readReceipt;
434: }
435: return $this->_setEmailSingle('_readReceipt', $email, $name, __d('cake_dev', 'Disposition-Notification-To requires only 1 email address.'));
436: }
437:
438: 439: 440: 441: 442: 443: 444: 445: 446:
447: public function returnPath($email = null, $name = null) {
448: if ($email === null) {
449: return $this->_returnPath;
450: }
451: return $this->_setEmailSingle('_returnPath', $email, $name, __d('cake_dev', 'Return-Path requires only 1 email address.'));
452: }
453:
454: 455: 456: 457: 458: 459: 460: 461:
462: public function to($email = null, $name = null) {
463: if ($email === null) {
464: return $this->_to;
465: }
466: return $this->_setEmail('_to', $email, $name);
467: }
468:
469: 470: 471: 472: 473: 474: 475: 476:
477: public function addTo($email, $name = null) {
478: return $this->_addEmail('_to', $email, $name);
479: }
480:
481: 482: 483: 484: 485: 486: 487: 488:
489: public function cc($email = null, $name = null) {
490: if ($email === null) {
491: return $this->_cc;
492: }
493: return $this->_setEmail('_cc', $email, $name);
494: }
495:
496: 497: 498: 499: 500: 501: 502: 503:
504: public function addCc($email, $name = null) {
505: return $this->_addEmail('_cc', $email, $name);
506: }
507:
508: 509: 510: 511: 512: 513: 514: 515:
516: public function bcc($email = null, $name = null) {
517: if ($email === null) {
518: return $this->_bcc;
519: }
520: return $this->_setEmail('_bcc', $email, $name);
521: }
522:
523: 524: 525: 526: 527: 528: 529: 530:
531: public function addBcc($email, $name = null) {
532: return $this->_addEmail('_bcc', $email, $name);
533: }
534:
535: 536: 537: 538: 539: 540:
541: public function charset($charset = null) {
542: if ($charset === null) {
543: return $this->charset;
544: }
545: $this->charset = $charset;
546: if (empty($this->headerCharset)) {
547: $this->headerCharset = $charset;
548: }
549: return $this->charset;
550: }
551:
552: 553: 554: 555: 556: 557:
558: public function headerCharset($charset = null) {
559: if ($charset === null) {
560: return $this->headerCharset;
561: }
562: return $this->headerCharset = $charset;
563: }
564:
565: 566: 567: 568: 569: 570: 571: 572:
573: public function emailPattern($regex = false) {
574: if ($regex === false) {
575: return $this->_emailPattern;
576: }
577: $this->_emailPattern = $regex;
578: return $this;
579: }
580:
581: 582: 583: 584: 585: 586: 587: 588: 589:
590: protected function _setEmail($varName, $email, $name) {
591: if (!is_array($email)) {
592: $this->_validateEmail($email, $varName);
593: if ($name === null) {
594: $name = $email;
595: }
596: $this->{$varName} = array($email => $name);
597: return $this;
598: }
599: $list = array();
600: foreach ($email as $key => $value) {
601: if (is_int($key)) {
602: $key = $value;
603: }
604: $this->_validateEmail($key, $varName);
605: $list[$key] = $value;
606: }
607: $this->{$varName} = $list;
608: return $this;
609: }
610:
611: 612: 613: 614: 615: 616: 617: 618:
619: protected function _validateEmail($email, $context) {
620: if ($this->_emailPattern === null) {
621: if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
622: return;
623: }
624: } elseif (preg_match($this->_emailPattern, $email)) {
625: return;
626: }
627: if ($email == '') {
628: throw new SocketException(__d('cake_dev', 'The email set for "%s" is empty.', $context));
629: }
630: throw new SocketException(__d('cake_dev', 'Invalid email set for "%s". You passed "%s".', $context, $email));
631: }
632:
633: 634: 635: 636: 637: 638: 639: 640: 641: 642: 643:
644: protected function _setEmailSingle($varName, $email, $name, $throwMessage) {
645: $current = $this->{$varName};
646: $this->_setEmail($varName, $email, $name);
647: if (count($this->{$varName}) !== 1) {
648: $this->{$varName} = $current;
649: throw new SocketException($throwMessage);
650: }
651: return $this;
652: }
653:
654: 655: 656: 657: 658: 659: 660: 661: 662: 663:
664: protected function _addEmail($varName, $email, $name) {
665: if (!is_array($email)) {
666: $this->_validateEmail($email, $varName);
667: if ($name === null) {
668: $name = $email;
669: }
670: $this->{$varName}[$email] = $name;
671: return $this;
672: }
673: $list = array();
674: foreach ($email as $key => $value) {
675: if (is_int($key)) {
676: $key = $value;
677: }
678: $this->_validateEmail($key, $varName);
679: $list[$key] = $value;
680: }
681: $this->{$varName} = array_merge($this->{$varName}, $list);
682: return $this;
683: }
684:
685: 686: 687: 688: 689: 690:
691: public function subject($subject = null) {
692: if ($subject === null) {
693: return $this->_subject;
694: }
695: $this->_subject = $this->_encode((string)$subject);
696: return $this;
697: }
698:
699: 700: 701: 702: 703: 704: 705:
706: public function setHeaders($headers) {
707: if (!is_array($headers)) {
708: throw new SocketException(__d('cake_dev', '$headers should be an array.'));
709: }
710: $this->_headers = $headers;
711: return $this;
712: }
713:
714: 715: 716: 717: 718: 719: 720:
721: public function addHeaders($headers) {
722: if (!is_array($headers)) {
723: throw new SocketException(__d('cake_dev', '$headers should be an array.'));
724: }
725: $this->_headers = array_merge($this->_headers, $headers);
726: return $this;
727: }
728:
729: 730: 731: 732: 733: 734: 735: 736: 737: 738: 739: 740: 741: 742: 743: 744: 745:
746: public function getHeaders($include = array()) {
747: if ($include == array_values($include)) {
748: $include = array_fill_keys($include, true);
749: }
750: $defaults = array_fill_keys(
751: array(
752: 'from', 'sender', 'replyTo', 'readReceipt', 'returnPath',
753: 'to', 'cc', 'bcc', 'subject'),
754: false
755: );
756: $include += $defaults;
757:
758: $headers = array();
759: $relation = array(
760: 'from' => 'From',
761: 'replyTo' => 'Reply-To',
762: 'readReceipt' => 'Disposition-Notification-To',
763: 'returnPath' => 'Return-Path'
764: );
765: foreach ($relation as $var => $header) {
766: if ($include[$var]) {
767: $var = '_' . $var;
768: $headers[$header] = current($this->_formatAddress($this->{$var}));
769: }
770: }
771: if ($include['sender']) {
772: if (key($this->_sender) === key($this->_from)) {
773: $headers['Sender'] = '';
774: } else {
775: $headers['Sender'] = current($this->_formatAddress($this->_sender));
776: }
777: }
778:
779: foreach (array('to', 'cc', 'bcc') as $var) {
780: if ($include[$var]) {
781: $classVar = '_' . $var;
782: $headers[ucfirst($var)] = implode(', ', $this->_formatAddress($this->{$classVar}));
783: }
784: }
785:
786: $headers += $this->_headers;
787: if (!isset($headers['X-Mailer'])) {
788: $headers['X-Mailer'] = static::EMAIL_CLIENT;
789: }
790: if (!isset($headers['Date'])) {
791: $headers['Date'] = date(DATE_RFC2822);
792: }
793: if ($this->_messageId !== false) {
794: if ($this->_messageId === true) {
795: $headers['Message-ID'] = '<' . str_replace('-', '', CakeText::uuid()) . '@' . $this->_domain . '>';
796: } else {
797: $headers['Message-ID'] = $this->_messageId;
798: }
799: }
800:
801: if ($include['subject']) {
802: $headers['Subject'] = $this->_subject;
803: }
804:
805: $headers['MIME-Version'] = '1.0';
806: if (!empty($this->_attachments)) {
807: $headers['Content-Type'] = 'multipart/mixed; boundary="' . $this->_boundary . '"';
808: } elseif ($this->_emailFormat === 'both') {
809: $headers['Content-Type'] = 'multipart/alternative; boundary="' . $this->_boundary . '"';
810: } elseif ($this->_emailFormat === 'text') {
811: $headers['Content-Type'] = 'text/plain; charset=' . $this->_getContentTypeCharset();
812: } elseif ($this->_emailFormat === 'html') {
813: $headers['Content-Type'] = 'text/html; charset=' . $this->_getContentTypeCharset();
814: }
815: $headers['Content-Transfer-Encoding'] = $this->_getContentTransferEncoding();
816:
817: return $headers;
818: }
819:
820: 821: 822: 823: 824: 825: 826: 827: 828: 829:
830: protected function _formatAddress($address) {
831: $return = array();
832: foreach ($address as $email => $alias) {
833: if ($email === $alias) {
834: $return[] = $email;
835: } else {
836: $encoded = $this->_encode($alias);
837: if (
838: $encoded === $alias && preg_match('/[^a-z0-9 ]/i', $encoded) ||
839: strpos($encoded, ',') !== false
840: ) {
841: $encoded = '"' . str_replace('"', '\"', $encoded) . '"';
842: }
843: $return[] = sprintf('%s <%s>', $encoded, $email);
844: }
845: }
846: return $return;
847: }
848:
849: 850: 851: 852: 853: 854: 855:
856: public function template($template = false, $layout = false) {
857: if ($template === false) {
858: return array(
859: 'template' => $this->_template,
860: 'layout' => $this->_layout
861: );
862: }
863: $this->_template = $template;
864: if ($layout !== false) {
865: $this->_layout = $layout;
866: }
867: return $this;
868: }
869:
870: 871: 872: 873: 874: 875:
876: public function viewRender($viewClass = null) {
877: if ($viewClass === null) {
878: return $this->_viewRender;
879: }
880: $this->_viewRender = $viewClass;
881: return $this;
882: }
883:
884: 885: 886: 887: 888: 889:
890: public function viewVars($viewVars = null) {
891: if ($viewVars === null) {
892: return $this->_viewVars;
893: }
894: $this->_viewVars = array_merge($this->_viewVars, (array)$viewVars);
895: return $this;
896: }
897:
898: 899: 900: 901: 902: 903:
904: public function theme($theme = null) {
905: if ($theme === null) {
906: return $this->_theme;
907: }
908: $this->_theme = $theme;
909: return $this;
910: }
911:
912: 913: 914: 915: 916: 917:
918: public function helpers($helpers = null) {
919: if ($helpers === null) {
920: return $this->_helpers;
921: }
922: $this->_helpers = (array)$helpers;
923: return $this;
924: }
925:
926: 927: 928: 929: 930: 931: 932:
933: public function emailFormat($format = null) {
934: if ($format === null) {
935: return $this->_emailFormat;
936: }
937: if (!in_array($format, $this->_emailFormatAvailable)) {
938: throw new SocketException(__d('cake_dev', 'Format not available.'));
939: }
940: $this->_emailFormat = $format;
941: return $this;
942: }
943:
944: 945: 946: 947: 948: 949:
950: public function transport($name = null) {
951: if ($name === null) {
952: return $this->_transportName;
953: }
954: $this->_transportName = (string)$name;
955: $this->_transportClass = null;
956: return $this;
957: }
958:
959: 960: 961: 962: 963: 964:
965: public function transportClass() {
966: if ($this->_transportClass) {
967: return $this->_transportClass;
968: }
969: list($plugin, $transportClassname) = pluginSplit($this->_transportName, true);
970: $transportClassname .= 'Transport';
971: App::uses($transportClassname, $plugin . 'Network/Email');
972: if (!class_exists($transportClassname)) {
973: throw new SocketException(__d('cake_dev', 'Class "%s" not found.', $transportClassname));
974: } elseif (!method_exists($transportClassname, 'send')) {
975: throw new SocketException(__d('cake_dev', 'The "%s" does not have a %s method.', $transportClassname, 'send()'));
976: }
977:
978: return $this->_transportClass = new $transportClassname();
979: }
980:
981: 982: 983: 984: 985: 986: 987:
988: public function messageId($message = null) {
989: if ($message === null) {
990: return $this->_messageId;
991: }
992: if (is_bool($message)) {
993: $this->_messageId = $message;
994: } else {
995: if (!preg_match('/^\<.+@.+\>$/', $message)) {
996: throw new SocketException(__d('cake_dev', 'Invalid format for Message-ID. The text should be something like "<[email protected]>"'));
997: }
998: $this->_messageId = $message;
999: }
1000: return $this;
1001: }
1002:
1003: 1004: 1005: 1006: 1007: 1008:
1009: public function domain($domain = null) {
1010: if ($domain === null) {
1011: return $this->_domain;
1012: }
1013: $this->_domain = $domain;
1014: return $this;
1015: }
1016:
1017: 1018: 1019: 1020: 1021: 1022: 1023: 1024: 1025: 1026: 1027: 1028: 1029: 1030: 1031: 1032: 1033: 1034: 1035: 1036: 1037: 1038: 1039: 1040: 1041: 1042: 1043: 1044: 1045: 1046: 1047: 1048: 1049: 1050: 1051: 1052: 1053: 1054: 1055: 1056: 1057: 1058: 1059: 1060: 1061: 1062: 1063:
1064: public function attachments($attachments = null) {
1065: if ($attachments === null) {
1066: return $this->_attachments;
1067: }
1068: $attach = array();
1069: foreach ((array)$attachments as $name => $fileInfo) {
1070: if (!is_array($fileInfo)) {
1071: $fileInfo = array('file' => $fileInfo);
1072: }
1073: if (!isset($fileInfo['file'])) {
1074: if (!isset($fileInfo['data'])) {
1075: throw new SocketException(__d('cake_dev', 'No file or data specified.'));
1076: }
1077: if (is_int($name)) {
1078: throw new SocketException(__d('cake_dev', 'No filename specified.'));
1079: }
1080: $fileInfo['data'] = chunk_split(base64_encode($fileInfo['data']), 76, "\r\n");
1081: } else {
1082: $fileName = $fileInfo['file'];
1083: $fileInfo['file'] = realpath($fileInfo['file']);
1084: if ($fileInfo['file'] === false || !file_exists($fileInfo['file'])) {
1085: throw new SocketException(__d('cake_dev', 'File not found: "%s"', $fileName));
1086: }
1087: if (is_int($name)) {
1088: $name = basename($fileInfo['file']);
1089: }
1090: }
1091: if (!isset($fileInfo['mimetype']) && isset($fileInfo['file']) && function_exists('mime_content_type')) {
1092: $fileInfo['mimetype'] = mime_content_type($fileInfo['file']);
1093: }
1094: if (!isset($fileInfo['mimetype'])) {
1095: $fileInfo['mimetype'] = 'application/octet-stream';
1096: }
1097: $attach[$name] = $fileInfo;
1098: }
1099: $this->_attachments = $attach;
1100: return $this;
1101: }
1102:
1103: 1104: 1105: 1106: 1107: 1108: 1109: 1110:
1111: public function addAttachments($attachments) {
1112: $current = $this->_attachments;
1113: $this->attachments($attachments);
1114: $this->_attachments = array_merge($current, $this->_attachments);
1115: return $this;
1116: }
1117:
1118: 1119: 1120: 1121: 1122: 1123:
1124: public function message($type = null) {
1125: switch ($type) {
1126: case static::MESSAGE_HTML:
1127: return $this->_htmlMessage;
1128: case static::MESSAGE_TEXT:
1129: return $this->_textMessage;
1130: }
1131: return $this->_message;
1132: }
1133:
1134: 1135: 1136: 1137: 1138: 1139: 1140: 1141: 1142: 1143: 1144: 1145: 1146: 1147: 1148: 1149:
1150: public function config($config = null) {
1151: if ($config === null) {
1152: return $this->_config;
1153: }
1154: if (!is_array($config)) {
1155: $config = (string)$config;
1156: }
1157:
1158: $this->_applyConfig($config);
1159: return $this;
1160: }
1161:
1162: 1163: 1164: 1165: 1166: 1167: 1168:
1169: public function send($content = null) {
1170: if (empty($this->_from)) {
1171: throw new SocketException(__d('cake_dev', 'From is not specified.'));
1172: }
1173: if (empty($this->_to) && empty($this->_cc) && empty($this->_bcc)) {
1174: throw new SocketException(__d('cake_dev', 'You need to specify at least one destination for to, cc or bcc.'));
1175: }
1176:
1177: if (is_array($content)) {
1178: $content = implode("\n", $content) . "\n";
1179: }
1180:
1181: $this->_message = $this->_render($this->_wrap($content));
1182:
1183: $contents = $this->transportClass()->send($this);
1184: if (!empty($this->_config['log'])) {
1185: $config = array(
1186: 'level' => LOG_DEBUG,
1187: 'scope' => 'email'
1188: );
1189: if ($this->_config['log'] !== true) {
1190: if (!is_array($this->_config['log'])) {
1191: $this->_config['log'] = array('level' => $this->_config['log']);
1192: }
1193: $config = $this->_config['log'] + $config;
1194: }
1195: CakeLog::write(
1196: $config['level'],
1197: PHP_EOL . $contents['headers'] . PHP_EOL . PHP_EOL . $contents['message'],
1198: $config['scope']
1199: );
1200: }
1201: return $contents;
1202: }
1203:
1204: 1205: 1206: 1207: 1208: 1209: 1210: 1211: 1212: 1213: 1214:
1215: public static function deliver($to = null, $subject = null, $message = null, $transportConfig = 'fast', $send = true) {
1216: $class = get_called_class();
1217:
1218: $instance = new $class($transportConfig);
1219: if ($to !== null) {
1220: $instance->to($to);
1221: }
1222: if ($subject !== null) {
1223: $instance->subject($subject);
1224: }
1225: if (is_array($message)) {
1226: $instance->viewVars($message);
1227: $message = null;
1228: } elseif ($message === null && array_key_exists('message', $config = $instance->config())) {
1229: $message = $config['message'];
1230: }
1231:
1232: if ($send === true) {
1233: $instance->send($message);
1234: }
1235:
1236: return $instance;
1237: }
1238:
1239: 1240: 1241: 1242: 1243: 1244: 1245: 1246:
1247: protected function _applyConfig($config) {
1248: if (is_string($config)) {
1249: if (!$this->_configInstance) {
1250: if (!class_exists($this->_configClass) && !config('email')) {
1251: throw new ConfigureException(__d('cake_dev', '%s not found.', CONFIG . 'email.php'));
1252: }
1253: $this->_configInstance = new $this->_configClass();
1254: }
1255: if (!isset($this->_configInstance->{$config})) {
1256: throw new ConfigureException(__d('cake_dev', 'Unknown email configuration "%s".', $config));
1257: }
1258: $config = $this->_configInstance->{$config};
1259: }
1260: $this->_config = $config + $this->_config;
1261: if (!empty($config['charset'])) {
1262: $this->charset = $config['charset'];
1263: }
1264: if (!empty($config['headerCharset'])) {
1265: $this->headerCharset = $config['headerCharset'];
1266: }
1267: if (empty($this->headerCharset)) {
1268: $this->headerCharset = $this->charset;
1269: }
1270: $simpleMethods = array(
1271: 'from', 'sender', 'to', 'replyTo', 'readReceipt', 'returnPath', 'cc', 'bcc',
1272: 'messageId', 'domain', 'subject', 'viewRender', 'viewVars', 'attachments',
1273: 'transport', 'emailFormat', 'theme', 'helpers', 'emailPattern'
1274: );
1275: foreach ($simpleMethods as $method) {
1276: if (isset($config[$method])) {
1277: $this->$method($config[$method]);
1278: unset($config[$method]);
1279: }
1280: }
1281: if (isset($config['headers'])) {
1282: $this->setHeaders($config['headers']);
1283: unset($config['headers']);
1284: }
1285:
1286: if (array_key_exists('template', $config)) {
1287: $this->_template = $config['template'];
1288: }
1289: if (array_key_exists('layout', $config)) {
1290: $this->_layout = $config['layout'];
1291: }
1292:
1293: $this->transportClass()->config($config);
1294: }
1295:
1296: 1297: 1298: 1299: 1300:
1301: public function reset() {
1302: $this->_to = array();
1303: $this->_from = array();
1304: $this->_sender = array();
1305: $this->_replyTo = array();
1306: $this->_readReceipt = array();
1307: $this->_returnPath = array();
1308: $this->_cc = array();
1309: $this->_bcc = array();
1310: $this->_messageId = true;
1311: $this->_subject = '';
1312: $this->_headers = array();
1313: $this->_layout = 'default';
1314: $this->_template = '';
1315: $this->_viewRender = 'View';
1316: $this->_viewVars = array();
1317: $this->_theme = null;
1318: $this->_helpers = array('Html');
1319: $this->_textMessage = '';
1320: $this->_htmlMessage = '';
1321: $this->_message = '';
1322: $this->_emailFormat = 'text';
1323: $this->_transportName = 'Mail';
1324: $this->_transportClass = null;
1325: $this->charset = 'utf-8';
1326: $this->headerCharset = null;
1327: $this->_attachments = array();
1328: $this->_config = array();
1329: $this->_emailPattern = static::EMAIL_PATTERN;
1330: return $this;
1331: }
1332:
1333: 1334: 1335: 1336: 1337: 1338:
1339: protected function _encode($text) {
1340: $internalEncoding = function_exists('mb_internal_encoding');
1341: if ($internalEncoding) {
1342: $restore = mb_internal_encoding();
1343: mb_internal_encoding($this->_appCharset);
1344: }
1345: if (empty($this->headerCharset)) {
1346: $this->headerCharset = $this->charset;
1347: }
1348: $return = mb_encode_mimeheader($text, $this->headerCharset, 'B');
1349: if ($internalEncoding) {
1350: mb_internal_encoding($restore);
1351: }
1352: return $return;
1353: }
1354:
1355: 1356: 1357: 1358: 1359: 1360: 1361: 1362:
1363: protected function _encodeString($text, $charset) {
1364: if ($this->_appCharset === $charset || !function_exists('mb_convert_encoding')) {
1365: return $text;
1366: }
1367: return mb_convert_encoding($text, $charset, $this->_appCharset);
1368: }
1369:
1370: 1371: 1372: 1373: 1374: 1375: 1376:
1377: protected function _wrap($message, $wrapLength = CakeEmail::LINE_LENGTH_MUST) {
1378: if (strlen($message) === 0) {
1379: return array('');
1380: }
1381: $message = str_replace(array("\r\n", "\r"), "\n", $message);
1382: $lines = explode("\n", $message);
1383: $formatted = array();
1384: $cut = ($wrapLength == CakeEmail::LINE_LENGTH_MUST);
1385:
1386: foreach ($lines as $line) {
1387: if (empty($line) && $line !== '0') {
1388: $formatted[] = '';
1389: continue;
1390: }
1391: if (strlen($line) < $wrapLength) {
1392: $formatted[] = $line;
1393: continue;
1394: }
1395: if (!preg_match('/<[a-z]+.*>/i', $line)) {
1396: $formatted = array_merge(
1397: $formatted,
1398: explode("\n", wordwrap($line, $wrapLength, "\n", $cut))
1399: );
1400: continue;
1401: }
1402:
1403: $tagOpen = false;
1404: $tmpLine = $tag = '';
1405: $tmpLineLength = 0;
1406: for ($i = 0, $count = strlen($line); $i < $count; $i++) {
1407: $char = $line[$i];
1408: if ($tagOpen) {
1409: $tag .= $char;
1410: if ($char === '>') {
1411: $tagLength = strlen($tag);
1412: if ($tagLength + $tmpLineLength < $wrapLength) {
1413: $tmpLine .= $tag;
1414: $tmpLineLength += $tagLength;
1415: } else {
1416: if ($tmpLineLength > 0) {
1417: $formatted = array_merge(
1418: $formatted,
1419: explode("\n", wordwrap(trim($tmpLine), $wrapLength, "\n", $cut))
1420: );
1421: $tmpLine = '';
1422: $tmpLineLength = 0;
1423: }
1424: if ($tagLength > $wrapLength) {
1425: $formatted[] = $tag;
1426: } else {
1427: $tmpLine = $tag;
1428: $tmpLineLength = $tagLength;
1429: }
1430: }
1431: $tag = '';
1432: $tagOpen = false;
1433: }
1434: continue;
1435: }
1436: if ($char === '<') {
1437: $tagOpen = true;
1438: $tag = '<';
1439: continue;
1440: }
1441: if ($char === ' ' && $tmpLineLength >= $wrapLength) {
1442: $formatted[] = $tmpLine;
1443: $tmpLineLength = 0;
1444: continue;
1445: }
1446: $tmpLine .= $char;
1447: $tmpLineLength++;
1448: if ($tmpLineLength === $wrapLength) {
1449: $nextChar = isset($line[$i + 1]) ? $line[$i + 1] : '';
1450: if ($nextChar === ' ' || $nextChar === '<') {
1451: $formatted[] = trim($tmpLine);
1452: $tmpLine = '';
1453: $tmpLineLength = 0;
1454: if ($nextChar === ' ') {
1455: $i++;
1456: }
1457: } else {
1458: $lastSpace = strrpos($tmpLine, ' ');
1459: if ($lastSpace === false) {
1460: continue;
1461: }
1462: $formatted[] = trim(substr($tmpLine, 0, $lastSpace));
1463: $tmpLine = substr($tmpLine, $lastSpace + 1);
1464:
1465: $tmpLineLength = strlen($tmpLine);
1466: }
1467: }
1468: }
1469: if (!empty($tmpLine)) {
1470: $formatted[] = $tmpLine;
1471: }
1472: }
1473: $formatted[] = '';
1474: return $formatted;
1475: }
1476:
1477: 1478: 1479: 1480: 1481:
1482: protected function _createBoundary() {
1483: if (!empty($this->_attachments) || $this->_emailFormat === 'both') {
1484: $this->_boundary = md5(uniqid(time()));
1485: }
1486: }
1487:
1488: 1489: 1490: 1491: 1492: 1493:
1494: protected function _attachFiles($boundary = null) {
1495: if ($boundary === null) {
1496: $boundary = $this->_boundary;
1497: }
1498:
1499: $msg = array();
1500: foreach ($this->_attachments as $filename => $fileInfo) {
1501: if (!empty($fileInfo['contentId'])) {
1502: continue;
1503: }
1504: $data = isset($fileInfo['data']) ? $fileInfo['data'] : $this->_readFile($fileInfo['file']);
1505:
1506: $msg[] = '--' . $boundary;
1507: $msg[] = 'Content-Type: ' . $fileInfo['mimetype'];
1508: $msg[] = 'Content-Transfer-Encoding: base64';
1509: if (!isset($fileInfo['contentDisposition']) ||
1510: $fileInfo['contentDisposition']
1511: ) {
1512: $msg[] = 'Content-Disposition: attachment; filename="' . $filename . '"';
1513: }
1514: $msg[] = '';
1515: $msg[] = $data;
1516: $msg[] = '';
1517: }
1518: return $msg;
1519: }
1520:
1521: 1522: 1523: 1524: 1525: 1526:
1527: protected function _readFile($path) {
1528: $File = new File($path);
1529: return chunk_split(base64_encode($File->read()));
1530: }
1531:
1532: 1533: 1534: 1535: 1536: 1537:
1538: protected function _attachInlineFiles($boundary = null) {
1539: if ($boundary === null) {
1540: $boundary = $this->_boundary;
1541: }
1542:
1543: $msg = array();
1544: foreach ($this->_attachments as $filename => $fileInfo) {
1545: if (empty($fileInfo['contentId'])) {
1546: continue;
1547: }
1548: $data = isset($fileInfo['data']) ? $fileInfo['data'] : $this->_readFile($fileInfo['file']);
1549:
1550: $msg[] = '--' . $boundary;
1551: $msg[] = 'Content-Type: ' . $fileInfo['mimetype'];
1552: $msg[] = 'Content-Transfer-Encoding: base64';
1553: $msg[] = 'Content-ID: <' . $fileInfo['contentId'] . '>';
1554: $msg[] = 'Content-Disposition: inline; filename="' . $filename . '"';
1555: $msg[] = '';
1556: $msg[] = $data;
1557: $msg[] = '';
1558: }
1559: return $msg;
1560: }
1561:
1562: 1563: 1564: 1565: 1566: 1567:
1568: protected function _render($content) {
1569: $this->_textMessage = $this->_htmlMessage = '';
1570:
1571: $content = implode("\n", $content);
1572: $rendered = $this->_renderTemplates($content);
1573:
1574: $this->_createBoundary();
1575: $msg = array();
1576:
1577: $contentIds = array_filter((array)Hash::extract($this->_attachments, '{s}.contentId'));
1578: $hasInlineAttachments = count($contentIds) > 0;
1579: $hasAttachments = !empty($this->_attachments);
1580: $hasMultipleTypes = count($rendered) > 1;
1581: $multiPart = ($hasAttachments || $hasMultipleTypes);
1582:
1583: $boundary = $relBoundary = $textBoundary = $this->_boundary;
1584:
1585: if ($hasInlineAttachments) {
1586: $msg[] = '--' . $boundary;
1587: $msg[] = 'Content-Type: multipart/related; boundary="rel-' . $boundary . '"';
1588: $msg[] = '';
1589: $relBoundary = $textBoundary = 'rel-' . $boundary;
1590: }
1591:
1592: if ($hasMultipleTypes && $hasAttachments) {
1593: $msg[] = '--' . $relBoundary;
1594: $msg[] = 'Content-Type: multipart/alternative; boundary="alt-' . $boundary . '"';
1595: $msg[] = '';
1596: $textBoundary = 'alt-' . $boundary;
1597: }
1598:
1599: if (isset($rendered['text'])) {
1600: if ($multiPart) {
1601: $msg[] = '--' . $textBoundary;
1602: $msg[] = 'Content-Type: text/plain; charset=' . $this->_getContentTypeCharset();
1603: $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding();
1604: $msg[] = '';
1605: }
1606: $this->_textMessage = $rendered['text'];
1607: $content = explode("\n", $this->_textMessage);
1608: $msg = array_merge($msg, $content);
1609: $msg[] = '';
1610: }
1611:
1612: if (isset($rendered['html'])) {
1613: if ($multiPart) {
1614: $msg[] = '--' . $textBoundary;
1615: $msg[] = 'Content-Type: text/html; charset=' . $this->_getContentTypeCharset();
1616: $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding();
1617: $msg[] = '';
1618: }
1619: $this->_htmlMessage = $rendered['html'];
1620: $content = explode("\n", $this->_htmlMessage);
1621: $msg = array_merge($msg, $content);
1622: $msg[] = '';
1623: }
1624:
1625: if ($textBoundary !== $relBoundary) {
1626: $msg[] = '--' . $textBoundary . '--';
1627: $msg[] = '';
1628: }
1629:
1630: if ($hasInlineAttachments) {
1631: $attachments = $this->_attachInlineFiles($relBoundary);
1632: $msg = array_merge($msg, $attachments);
1633: $msg[] = '';
1634: $msg[] = '--' . $relBoundary . '--';
1635: $msg[] = '';
1636: }
1637:
1638: if ($hasAttachments) {
1639: $attachments = $this->_attachFiles($boundary);
1640: $msg = array_merge($msg, $attachments);
1641: }
1642: if ($hasAttachments || $hasMultipleTypes) {
1643: $msg[] = '';
1644: $msg[] = '--' . $boundary . '--';
1645: $msg[] = '';
1646: }
1647: return $msg;
1648: }
1649:
1650: 1651: 1652: 1653: 1654:
1655: protected function _getTypes() {
1656: $types = array($this->_emailFormat);
1657: if ($this->_emailFormat === 'both') {
1658: $types = array('html', 'text');
1659: }
1660: return $types;
1661: }
1662:
1663: 1664: 1665: 1666: 1667: 1668: 1669: 1670:
1671: protected function _renderTemplates($content) {
1672: $types = $this->_getTypes();
1673: $rendered = array();
1674: if (empty($this->_template)) {
1675: foreach ($types as $type) {
1676: $rendered[$type] = $this->_encodeString($content, $this->charset);
1677: }
1678: return $rendered;
1679: }
1680: $viewClass = $this->_viewRender;
1681: if ($viewClass !== 'View') {
1682: list($plugin, $viewClass) = pluginSplit($viewClass, true);
1683: $viewClass .= 'View';
1684: App::uses($viewClass, $plugin . 'View');
1685: }
1686:
1687:
1688: $View = new $viewClass(null);
1689: $View->viewVars = $this->_viewVars;
1690: $View->helpers = $this->_helpers;
1691:
1692: if ($this->_theme) {
1693: $View->theme = $this->_theme;
1694: }
1695:
1696: $View->loadHelpers();
1697:
1698: list($templatePlugin, $template) = pluginSplit($this->_template);
1699: list($layoutPlugin, $layout) = pluginSplit($this->_layout);
1700: if ($templatePlugin) {
1701: $View->plugin = $templatePlugin;
1702: } elseif ($layoutPlugin) {
1703: $View->plugin = $layoutPlugin;
1704: }
1705:
1706: if ($View->get('content') === null) {
1707: $View->set('content', $content);
1708: }
1709:
1710:
1711:
1712: if ($this->_layout === null) {
1713: $this->_layout = false;
1714: }
1715:
1716: foreach ($types as $type) {
1717: $View->hasRendered = false;
1718: $View->viewPath = $View->layoutPath = 'Emails' . DS . $type;
1719:
1720: $render = $View->render($this->_template, $this->_layout);
1721: $render = str_replace(array("\r\n", "\r"), "\n", $render);
1722: $rendered[$type] = $this->_encodeString($render, $this->charset);
1723: }
1724:
1725: foreach ($rendered as $type => $content) {
1726: $rendered[$type] = $this->_wrap($content);
1727: $rendered[$type] = implode("\n", $rendered[$type]);
1728: $rendered[$type] = rtrim($rendered[$type], "\n");
1729: }
1730: return $rendered;
1731: }
1732:
1733: 1734: 1735: 1736: 1737:
1738: protected function _getContentTransferEncoding() {
1739: $charset = strtoupper($this->charset);
1740: if (in_array($charset, $this->_charset8bit)) {
1741: return '8bit';
1742: }
1743: return '7bit';
1744: }
1745:
1746: 1747: 1748: 1749: 1750: 1751: 1752: 1753:
1754: protected function _getContentTypeCharset() {
1755: $charset = strtoupper($this->charset);
1756: if (array_key_exists($charset, $this->_contentTypeCharset)) {
1757: return strtoupper($this->_contentTypeCharset[$charset]);
1758: }
1759: return strtoupper($this->charset);
1760: }
1761:
1762: }
1763: