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