1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
20: App::import('Core', 'Multibyte');
21: App::import('Core', 'String');
22:
23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33:
34: class EmailComponent extends Object{
35:
36: 37: 38: 39: 40: 41:
42: var $to = null;
43:
44: 45: 46: 47: 48: 49:
50: var $from = null;
51:
52: 53: 54: 55: 56: 57:
58: var $replyTo = null;
59:
60: 61: 62: 63: 64: 65:
66: var $readReceipt = null;
67:
68: 69: 70: 71: 72: 73: 74: 75: 76:
77: var $return = null;
78:
79: 80: 81: 82: 83: 84: 85: 86: 87:
88: var $cc = array();
89:
90: 91: 92: 93: 94: 95: 96: 97: 98:
99: var $bcc = array();
100:
101: 102: 103: 104: 105: 106: 107:
108: var $date = null;
109:
110: 111: 112: 113: 114: 115:
116: var $subject = null;
117:
118: 119: 120: 121: 122: 123: 124:
125: var $headers = array();
126:
127: 128: 129: 130: 131: 132: 133: 134:
135: var $additionalParams = null;
136:
137: 138: 139: 140: 141: 142:
143: var $layout = 'default';
144:
145: 146: 147: 148: 149: 150:
151: var $template = null;
152:
153: 154: 155: 156: 157: 158:
159: var $lineLength = 70;
160:
161: 162: 163: 164: 165: 166: 167: 168: 169: 170:
171: var $lineFeed = PHP_EOL;
172:
173: 174: 175:
176: var $_lineLength = null;
177:
178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188:
189: var $sendAs = 'text';
190:
191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201:
202: var $delivery = 'mail';
203:
204: 205: 206: 207: 208: 209:
210: var $charset = 'utf-8';
211:
212: 213: 214: 215: 216: 217: 218: 219:
220: var $attachments = array();
221:
222: 223: 224: 225: 226: 227:
228: var $xMailer = 'CakePHP Email Component';
229:
230: 231: 232: 233: 234: 235:
236: var $filePaths = array();
237:
238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252:
253: var $smtpOptions = array();
254:
255: 256: 257: 258: 259: 260: 261:
262: var $smtpError = null;
263:
264: 265: 266: 267: 268: 269:
270: var $textMessage = null;
271:
272: 273: 274: 275: 276: 277:
278: var $htmlMessage = null;
279:
280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291:
292: var $messageId = true;
293:
294: 295: 296: 297: 298: 299:
300: var $__header = array();
301:
302: 303: 304: 305: 306: 307:
308: var $__boundary = null;
309:
310: 311: 312: 313: 314: 315:
316: var $__message = array();
317:
318: 319: 320: 321: 322: 323:
324: var $__smtpConnection = null;
325:
326: 327: 328: 329: 330: 331:
332: function initialize(&$controller, $settings = array()) {
333: $this->Controller =& $controller;
334: if (Configure::read('App.encoding') !== null) {
335: $this->charset = Configure::read('App.encoding');
336: }
337: $this->_set($settings);
338: }
339:
340: 341: 342: 343: 344: 345:
346: function startup(&$controller) {}
347:
348: 349: 350: 351: 352: 353: 354: 355: 356: 357:
358: function send($content = null, $template = null, $layout = null) {
359: $this->_createHeader();
360:
361: if ($template) {
362: $this->template = $template;
363: }
364:
365: if ($layout) {
366: $this->layout = $layout;
367: }
368:
369: if (is_array($content)) {
370: $content = implode("\n", $content) . "\n";
371: }
372:
373: $this->htmlMessage = $this->textMessage = null;
374: if ($content) {
375: if ($this->sendAs === 'html') {
376: $this->htmlMessage = $content;
377: } elseif ($this->sendAs === 'text') {
378: $this->textMessage = $content;
379: } else {
380: $this->htmlMessage = $this->textMessage = $content;
381: }
382: }
383:
384: if ($this->sendAs === 'text') {
385: $message = $this->_wrap($content);
386: } else {
387: $message = $this->_wrap($content, 998);
388: }
389:
390: if ($this->template === null) {
391: $message = $this->_formatMessage($message);
392: } else {
393: $message = $this->_render($message);
394: }
395:
396: $message[] = '';
397: $this->__message = $message;
398:
399: if (!empty($this->attachments)) {
400: $this->_attachFiles();
401: }
402:
403: if (!empty($this->attachments)) {
404: $this->__message[] = '';
405: $this->__message[] = '--' . $this->__boundary . '--';
406: $this->__message[] = '';
407: }
408:
409: $_method = '_' . $this->delivery;
410: $sent = $this->$_method();
411:
412: $this->__header = array();
413: $this->__message = array();
414:
415: return $sent;
416: }
417:
418: 419: 420: 421: 422: 423:
424: function reset() {
425: $this->template = null;
426: $this->to = array();
427: $this->from = null;
428: $this->replyTo = null;
429: $this->return = null;
430: $this->cc = array();
431: $this->bcc = array();
432: $this->headers = array();
433: $this->subject = null;
434: $this->additionalParams = null;
435: $this->date = null;
436: $this->smtpError = null;
437: $this->attachments = array();
438: $this->htmlMessage = null;
439: $this->textMessage = null;
440: $this->messageId = true;
441: $this->delivery = 'mail';
442: $this->__header = array();
443: $this->__boundary = null;
444: $this->__message = array();
445: }
446:
447: 448: 449: 450: 451: 452: 453:
454: function _render($content) {
455: $viewClass = $this->Controller->view;
456:
457: if ($viewClass != 'View') {
458: list($plugin, $viewClass) = pluginSplit($viewClass);
459: $viewClass = $viewClass . 'View';
460: App::import('View', $this->Controller->view);
461: }
462:
463: $View = new $viewClass($this->Controller);
464: $View->layout = $this->layout;
465: $msg = array();
466:
467: $content = implode("\n", $content);
468:
469: if ($this->sendAs === 'both') {
470: $htmlContent = $content;
471: if (!empty($this->attachments)) {
472: $msg[] = '--' . $this->__boundary;
473: $msg[] = 'Content-Type: multipart/alternative; boundary="alt-' . $this->__boundary . '"';
474: $msg[] = '';
475: }
476: $msg[] = '--alt-' . $this->__boundary;
477: $msg[] = 'Content-Type: text/plain; charset=' . $this->charset;
478: $msg[] = 'Content-Transfer-Encoding: 7bit';
479: $msg[] = '';
480:
481: $content = $View->element('email' . DS . 'text' . DS . $this->template, array('content' => $content), true);
482: $View->layoutPath = 'email' . DS . 'text';
483: $content = explode("\n", $this->textMessage = str_replace(array("\r\n", "\r"), "\n", $View->renderLayout($content)));
484:
485: $msg = array_merge($msg, $content);
486:
487: $msg[] = '';
488: $msg[] = '--alt-' . $this->__boundary;
489: $msg[] = 'Content-Type: text/html; charset=' . $this->charset;
490: $msg[] = 'Content-Transfer-Encoding: 7bit';
491: $msg[] = '';
492:
493: $htmlContent = $View->element('email' . DS . 'html' . DS . $this->template, array('content' => $htmlContent), true);
494: $View->layoutPath = 'email' . DS . 'html';
495: $htmlContent = explode("\n", $this->htmlMessage = str_replace(array("\r\n", "\r"), "\n", $View->renderLayout($htmlContent)));
496:
497: $msg = array_merge($msg, $htmlContent);
498:
499: $msg[] = '';
500: $msg[] = '--alt-' . $this->__boundary . '--';
501: $msg[] = '';
502:
503: ClassRegistry::removeObject('view');
504: return $msg;
505: }
506:
507: if (!empty($this->attachments)) {
508: if ($this->sendAs === 'html') {
509: $msg[] = '';
510: $msg[] = '--' . $this->__boundary;
511: $msg[] = 'Content-Type: text/html; charset=' . $this->charset;
512: $msg[] = 'Content-Transfer-Encoding: 7bit';
513: $msg[] = '';
514: } else {
515: $msg[] = '--' . $this->__boundary;
516: $msg[] = 'Content-Type: text/plain; charset=' . $this->charset;
517: $msg[] = 'Content-Transfer-Encoding: 7bit';
518: $msg[] = '';
519: }
520: }
521:
522: $content = $View->element('email' . DS . $this->sendAs . DS . $this->template, array('content' => $content), true);
523: $View->layoutPath = 'email' . DS . $this->sendAs;
524: $content = explode("\n", $rendered = str_replace(array("\r\n", "\r"), "\n", $View->renderLayout($content)));
525:
526: if ($this->sendAs === 'html') {
527: $this->htmlMessage = $rendered;
528: } else {
529: $this->textMessage = $rendered;
530: }
531:
532: $msg = array_merge($msg, $content);
533: ClassRegistry::removeObject('view');
534:
535: return $msg;
536: }
537:
538: 539: 540: 541: 542:
543: function _createboundary() {
544: $this->__boundary = md5(uniqid(time()));
545: }
546:
547: 548: 549: 550: 551: 552:
553: function header($headers) {
554: foreach ($headers as $header => $value) {
555: $this->__header[] = sprintf('%s: %s', trim($header), trim($value));
556: }
557: }
558: 559: 560: 561: 562: 563:
564: function _createHeader() {
565: $headers = array();
566:
567: if ($this->delivery == 'smtp') {
568: $headers['To'] = implode(', ', array_map(array($this, '_formatAddress'), (array)$this->to));
569: }
570: $headers['From'] = $this->_formatAddress($this->from);
571:
572: if (!empty($this->replyTo)) {
573: $headers['Reply-To'] = $this->_formatAddress($this->replyTo);
574: }
575: if (!empty($this->return)) {
576: $headers['Return-Path'] = $this->_formatAddress($this->return);
577: }
578: if (!empty($this->readReceipt)) {
579: $headers['Disposition-Notification-To'] = $this->_formatAddress($this->readReceipt);
580: }
581:
582: if (!empty($this->cc)) {
583: $headers['Cc'] = implode(', ', array_map(array($this, '_formatAddress'), (array)$this->cc));
584: }
585:
586: if (!empty($this->bcc) && $this->delivery != 'smtp') {
587: $headers['Bcc'] = implode(', ', array_map(array($this, '_formatAddress'), (array)$this->bcc));
588: }
589: if ($this->delivery == 'smtp') {
590: $headers['Subject'] = $this->_encode($this->subject);
591: }
592:
593: if ($this->messageId !== false) {
594: if ($this->messageId === true) {
595: $headers['Message-ID'] = '<' . str_replace('-', '', String::uuid()) . '@' . env('HTTP_HOST') . '>';
596: } else {
597: $headers['Message-ID'] = $this->messageId;
598: }
599: }
600:
601: $date = $this->date;
602: if ($date == false) {
603: $date = date(DATE_RFC2822);
604: }
605: $headers['Date'] = $date;
606:
607: $headers['X-Mailer'] = $this->xMailer;
608:
609: if (!empty($this->headers)) {
610: foreach ($this->headers as $key => $val) {
611: $headers['X-' . $key] = $val;
612: }
613: }
614:
615: if (!empty($this->attachments) || $this->sendAs === 'both') {
616: $this->_createBoundary();
617: }
618:
619: if (!empty($this->attachments)) {
620: $headers['Content-Type'] = 'multipart/mixed; boundary="' . $this->__boundary . '"';
621: } elseif ($this->sendAs === 'text') {
622: $headers['Content-Type'] = 'text/plain; charset=' . $this->charset;
623: } elseif ($this->sendAs === 'html') {
624: $headers['Content-Type'] = 'text/html; charset=' . $this->charset;
625: } elseif ($this->sendAs === 'both') {
626: $headers['Content-Type'] = 'multipart/alternative; boundary="alt-' . $this->__boundary . '"';
627: }
628:
629: $headers['MIME-Version'] = '1.0';
630: $headers['Content-Transfer-Encoding'] = '7bit';
631:
632: $this->header($headers);
633: }
634:
635: 636: 637: 638: 639: 640:
641: function _formatMessage($message) {
642: if (!empty($this->attachments)) {
643: $prefix = array('--' . $this->__boundary);
644: if ($this->sendAs === 'text') {
645: $prefix[] = 'Content-Type: text/plain; charset=' . $this->charset;
646: } elseif ($this->sendAs === 'html') {
647: $prefix[] = 'Content-Type: text/html; charset=' . $this->charset;
648: } elseif ($this->sendAs === 'both') {
649: $prefix[] = 'Content-Type: multipart/alternative; boundary="alt-' . $this->__boundary . '"';
650: }
651: $prefix[] = 'Content-Transfer-Encoding: 7bit';
652: $prefix[] = '';
653: $message = array_merge($prefix, $message);
654: }
655: return $message;
656: }
657:
658: 659: 660: 661: 662: 663:
664: function _attachFiles() {
665: $files = array();
666: foreach ($this->attachments as $filename => $attachment) {
667: $file = $this->_findFiles($attachment);
668: if (!empty($file)) {
669: if (is_int($filename)) {
670: $filename = basename($file);
671: }
672: $files[$filename] = $file;
673: }
674: }
675:
676: foreach ($files as $filename => $file) {
677: $handle = fopen($file, 'rb');
678: $data = fread($handle, filesize($file));
679: $data = chunk_split(base64_encode($data)) ;
680: fclose($handle);
681:
682: $this->__message[] = '--' . $this->__boundary;
683: $this->__message[] = 'Content-Type: application/octet-stream';
684: $this->__message[] = 'Content-Transfer-Encoding: base64';
685: $this->__message[] = 'Content-Disposition: attachment; filename="' . basename($filename) . '"';
686: $this->__message[] = '';
687: $this->__message[] = $data;
688: $this->__message[] = '';
689: }
690: }
691:
692: 693: 694: 695: 696: 697: 698:
699: function _findFiles($attachment) {
700: if (file_exists($attachment)) {
701: return $attachment;
702: }
703: foreach ($this->filePaths as $path) {
704: if (file_exists($path . DS . $attachment)) {
705: $file = $path . DS . $attachment;
706: return $file;
707: }
708: }
709: return null;
710: }
711:
712: 713: 714: 715: 716: 717: 718: 719:
720: function _wrap($message, $lineLength = null) {
721: $message = $this->_strip($message, true);
722: $message = str_replace(array("\r\n","\r"), "\n", $message);
723: $lines = explode("\n", $message);
724: $formatted = array();
725:
726: if ($this->_lineLength !== null) {
727: trigger_error(__('_lineLength cannot be accessed please use lineLength', true), E_USER_WARNING);
728: $this->lineLength = $this->_lineLength;
729: }
730:
731: if (!$lineLength) {
732: $lineLength = $this->lineLength;
733: }
734:
735: foreach ($lines as $line) {
736: if (substr($line, 0, 1) == '.') {
737: $line = '.' . $line;
738: }
739: $formatted = array_merge($formatted, explode("\n", wordwrap($line, $lineLength, "\n", true)));
740: }
741: $formatted[] = '';
742: return $formatted;
743: }
744:
745: 746: 747: 748: 749: 750: 751:
752: function _encode($subject) {
753: $subject = $this->_strip($subject);
754:
755: $nl = "\r\n";
756: if ($this->delivery == 'mail') {
757: $nl = '';
758: }
759: $internalEncoding = function_exists('mb_internal_encoding');
760: if ($internalEncoding) {
761: $restore = mb_internal_encoding();
762: mb_internal_encoding($this->charset);
763: }
764: $return = mb_encode_mimeheader($subject, $this->charset, 'B', $nl);
765: if ($internalEncoding) {
766: mb_internal_encoding($restore);
767: }
768: return $return;
769: }
770:
771: 772: 773: 774: 775: 776: 777:
778: function _formatAddress($string, $smtp = false) {
779: $hasAlias = preg_match('/((.*))?\s?<(.+)>/', $string, $matches);
780: if ($smtp && $hasAlias) {
781: return $this->_strip('<' . $matches[3] . '>');
782: } elseif ($smtp) {
783: return $this->_strip('<' . $string . '>');
784: }
785:
786: if ($hasAlias && !empty($matches[2])) {
787: return $this->_encode(trim($matches[2])) . $this->_strip(' <' . $matches[3] . '>');
788: }
789: return $this->_strip($string);
790: }
791:
792: 793: 794: 795: 796: 797: 798: 799: 800:
801: function _strip($value, $message = false) {
802: $search = '%0a|%0d|Content-(?:Type|Transfer-Encoding)\:';
803: $search .= '|charset\=|mime-version\:|multipart/mixed|(?:[\n\r]+to|b?cc)\:.*';
804:
805: if ($message !== true) {
806: $search .= '|\r|\n';
807: }
808: $search = '#(?:' . $search . ')#i';
809: while (preg_match($search, $value)) {
810: $value = preg_replace($search, '', $value);
811: }
812: return $value;
813: }
814:
815: 816: 817: 818: 819: 820:
821: function _mail() {
822: $header = implode($this->lineFeed, $this->__header);
823: $message = implode($this->lineFeed, $this->__message);
824: if (is_array($this->to)) {
825: $to = implode(', ', array_map(array($this, '_formatAddress'), $this->to));
826: } else {
827: $to = $this->to;
828: }
829: if (ini_get('safe_mode')) {
830: return @mail($to, $this->_encode($this->subject), $message, $header);
831: }
832: return @mail($to, $this->_encode($this->subject), $message, $header, $this->additionalParams);
833: }
834:
835:
836: 837: 838: 839: 840: 841: 842:
843: function _getSocket($config) {
844: $this->__smtpConnection =& new CakeSocket($config);
845: }
846:
847: 848: 849: 850: 851: 852:
853: function _smtp() {
854: App::import('Core', array('CakeSocket'));
855:
856: $defaults = array(
857: 'host' => 'localhost',
858: 'port' => 25,
859: 'protocol' => 'smtp',
860: 'timeout' => 30
861: );
862: $this->smtpOptions = array_merge($defaults, $this->smtpOptions);
863: $this->_getSocket($this->smtpOptions);
864:
865: if (!$this->__smtpConnection->connect()) {
866: $this->smtpError = $this->__smtpConnection->lastError();
867: return false;
868: } elseif (!$this->_smtpSend(null, '220')) {
869: return false;
870: }
871:
872: $httpHost = env('HTTP_HOST');
873:
874: if (isset($this->smtpOptions['client'])) {
875: $host = $this->smtpOptions['client'];
876: } elseif (!empty($httpHost)) {
877: list($host) = explode(':', $httpHost);
878: } else {
879: $host = 'localhost';
880: }
881:
882: if (!$this->_smtpSend("EHLO {$host}", '250') && !$this->_smtpSend("HELO {$host}", '250')) {
883: return false;
884: }
885:
886: if (isset($this->smtpOptions['username']) && isset($this->smtpOptions['password'])) {
887: $authRequired = $this->_smtpSend('AUTH LOGIN', '334|503');
888: if ($authRequired == '334') {
889: if (!$this->_smtpSend(base64_encode($this->smtpOptions['username']), '334')) {
890: return false;
891: }
892: if (!$this->_smtpSend(base64_encode($this->smtpOptions['password']), '235')) {
893: return false;
894: }
895: } elseif ($authRequired != '503') {
896: return false;
897: }
898: }
899:
900: if (!$this->_smtpSend('MAIL FROM: ' . $this->_formatAddress($this->from, true))) {
901: return false;
902: }
903:
904: if (!is_array($this->to)) {
905: $tos = array_map('trim', explode(',', $this->to));
906: } else {
907: $tos = $this->to;
908: }
909: foreach ($tos as $to) {
910: if (!$this->_smtpSend('RCPT TO: ' . $this->_formatAddress($to, true))) {
911: return false;
912: }
913: }
914:
915: foreach ($this->cc as $cc) {
916: if (!$this->_smtpSend('RCPT TO: ' . $this->_formatAddress($cc, true))) {
917: return false;
918: }
919: }
920: foreach ($this->bcc as $bcc) {
921: if (!$this->_smtpSend('RCPT TO: ' . $this->_formatAddress($bcc, true))) {
922: return false;
923: }
924: }
925:
926: if (!$this->_smtpSend('DATA', '354')) {
927: return false;
928: }
929:
930: $header = implode("\r\n", $this->__header);
931: $message = implode("\r\n", $this->__message);
932: if (!$this->_smtpSend($header . "\r\n\r\n" . $message . "\r\n\r\n\r\n.")) {
933: return false;
934: }
935: $this->_smtpSend('QUIT', false);
936:
937: $this->__smtpConnection->disconnect();
938: return true;
939: }
940:
941: 942: 943: 944: 945: 946: 947: 948:
949: function _smtpSend($data, $checkCode = '250') {
950: if (!is_null($data)) {
951: $this->__smtpConnection->write($data . "\r\n");
952: }
953: while ($checkCode !== false) {
954: $response = '';
955: $startTime = time();
956: while (substr($response, -2) !== "\r\n" && ((time() - $startTime) < $this->smtpOptions['timeout'])) {
957: $response .= $this->__smtpConnection->read();
958: }
959: if (substr($response, -2) !== "\r\n") {
960: $this->smtpError = 'timeout';
961: return false;
962: }
963: $response = end(explode("\r\n", rtrim($response, "\r\n")));
964:
965: if (preg_match('/^(' . $checkCode . ')(.)/', $response, $code)) {
966: if ($code[2] === '-') {
967: continue;
968: }
969: return $code[1];
970: }
971: $this->smtpError = $response;
972: return false;
973: }
974: return true;
975: }
976:
977: 978: 979: 980: 981: 982:
983: function _debug() {
984: $nl = "\n";
985: $header = implode($nl, $this->__header);
986: $message = implode($nl, $this->__message);
987: $fm = '<pre>';
988:
989: if (is_array($this->to)) {
990: $to = implode(', ', array_map(array($this, '_formatAddress'), $this->to));
991: } else {
992: $to = $this->to;
993: }
994: $fm .= sprintf('%s %s%s', 'To:', $to, $nl);
995: $fm .= sprintf('%s %s%s', 'From:', $this->from, $nl);
996: $fm .= sprintf('%s %s%s', 'Subject:', $this->_encode($this->subject), $nl);
997: $fm .= sprintf('%s%3$s%3$s%s', 'Header:', $header, $nl);
998: $fm .= sprintf('%s%3$s%3$s%s', 'Parameters:', $this->additionalParams, $nl);
999: $fm .= sprintf('%s%3$s%3$s%s', 'Message:', $message, $nl);
1000: $fm .= '</pre>';
1001:
1002: if (isset($this->Controller->Session)) {
1003: $this->Controller->Session->setFlash($fm, 'default', null, 'email');
1004: return true;
1005: }
1006: return $fm;
1007: }
1008: }
1009: