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