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