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