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