cake/libs/controller/components/email.php

1 <?php
2 /**
3 * Email Component
4 *
5 * PHP versions 4 and 5
6 *
7 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
8 * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
9 *
10 * Licensed under The MIT License
11 * Redistributions of files must retain the above copyright notice.
12 *
13 * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
14 * @link http://cakephp.org CakePHP(tm) Project
15 * @package cake
16 * @subpackage cake.cake.libs.controller.components
17 * @since CakePHP(tm) v 1.2.0.3467
18 * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
19 */
20 App::import('Core', 'Multibyte');
21  
22 /**
23 * EmailComponent
24 *
25 * This component is used for handling Internet Message Format based
26 * based on the standard outlined in http://www.rfc-editor.org/rfc/rfc2822.txt
27 *
28 * @package cake
29 * @subpackage cake.cake.libs.controller.components
30 * @link http://book.cakephp.org/view/1283/Email
31 *
32 */
33 class EmailComponent extends Object{
34  
35 /**
36 * Recipient of the email
37 *
38 * @var string
39 * @access public
40 */
41 var $to = null;
42  
43 /**
44 * The mail which the email is sent from
45 *
46 * @var string
47 * @access public
48 */
49 var $from = null;
50  
51 /**
52 * The email the recipient will reply to
53 *
54 * @var string
55 * @access public
56 */
57 var $replyTo = null;
58  
59 /**
60 * The read receipt email
61 *
62 * @var string
63 * @access public
64 */
65 var $readReceipt = null;
66  
67 /**
68 * The mail that will be used in case of any errors like
69 * - Remote mailserver down
70 * - Remote user has exceeded his quota
71 * - Unknown user
72 *
73 * @var string
74 * @access public
75 */
76 var $return = null;
77  
78 /**
79 * Carbon Copy
80 *
81 * List of email's that should receive a copy of the email.
82 * The Recipient WILL be able to see this list
83 *
84 * @var array
85 * @access public
86 */
87 var $cc = array();
88  
89 /**
90 * Blind Carbon Copy
91 *
92 * List of email's that should receive a copy of the email.
93 * The Recipient WILL NOT be able to see this list
94 *
95 * @var array
96 * @access public
97 */
98 var $bcc = array();
99  
100 /**
101 * The date to put in the Date: header. This should be a date
102 * conformant with the RFC2822 standard. Leave null, to have
103 * today's date generated.
104 *
105 * @var string
106 */
107 var $date = null;
108  
109 /**
110 * The subject of the email
111 *
112 * @var string
113 * @access public
114 */
115 var $subject = null;
116  
117 /**
118 * Associative array of a user defined headers
119 * Keys will be prefixed 'X-' as per RFC2822 Section 4.7.5
120 *
121 * @var array
122 * @access public
123 */
124 var $headers = array();
125  
126 /**
127 * List of additional headers
128 *
129 * These will NOT be used if you are using safemode and mail()
130 *
131 * @var string
132 * @access public
133 */
134 var $additionalParams = null;
135  
136 /**
137 * Layout for the View
138 *
139 * @var string
140 * @access public
141 */
142 var $layout = 'default';
143  
144 /**
145 * Template for the view
146 *
147 * @var string
148 * @access public
149 */
150 var $template = null;
151  
152 /**
153 * as per RFC2822 Section 2.1.1
154 *
155 * @var integer
156 * @access public
157 */
158 var $lineLength = 70;
159  
160 /**
161 * Line feed character(s) to be used when sending using mail() function
162 * By default PHP_EOL is used.
163 * RFC2822 requires it to be CRLF but some Unix
164 * mail transfer agents replace LF by CRLF automatically
165 * (which leads to doubling CR if CRLF is used).
166 *
167 * @var string
168 * @access public
169 */
170 var $lineFeed = PHP_EOL;
171  
172 /**
173 * @deprecated see lineLength
174 */
175 var $_lineLength = null;
176  
177 /**
178 * What format should the email be sent in
179 *
180 * Supported formats:
181 * - text
182 * - html
183 * - both
184 *
185 * @var string
186 * @access public
187 */
188 var $sendAs = 'text';
189  
190 /**
191 * What method should the email be sent by
192 *
193 * Supported methods:
194 * - mail
195 * - smtp
196 * - debug
197 *
198 * @var string
199 * @access public
200 */
201 var $delivery = 'mail';
202  
203 /**
204 * charset the email is sent in
205 *
206 * @var string
207 * @access public
208 */
209 var $charset = 'utf-8';
210  
211 /**
212 * List of files that should be attached to the email.
213 *
214 * Can be both absolute and relative paths
215 *
216 * @var array
217 * @access public
218 */
219 var $attachments = array();
220  
221 /**
222 * What mailer should EmailComponent identify itself as
223 *
224 * @var string
225 * @access public
226 */
227 var $xMailer = 'CakePHP Email Component';
228  
229 /**
230 * The list of paths to search if an attachment isnt absolute
231 *
232 * @var array
233 * @access public
234 */
235 var $filePaths = array();
236  
237 /**
238 * List of options to use for smtp mail method
239 *
240 * Options is:
241 * - port
242 * - host
243 * - timeout
244 * - username
245 * - password
246 * - client
247 *
248 * @var array
249 * @access public
250 * @link http://book.cakephp.org/view/1290/Sending-A-Message-Using-SMTP
251 */
252 var $smtpOptions = array();
253  
254 /**
255 * Placeholder for any errors that might happen with the
256 * smtp mail methods
257 *
258 * @var string
259 * @access public
260 */
261 var $smtpError = null;
262  
263 /**
264 * Contains the rendered plain text message if one was sent.
265 *
266 * @var string
267 * @access public
268 */
269 var $textMessage = null;
270  
271 /**
272 * Contains the rendered HTML message if one was sent.
273 *
274 * @var string
275 * @access public
276 */
277 var $htmlMessage = null;
278  
279 /**
280 * Whether to generate a Message-ID header for the
281 * e-mail. True to generate a Message-ID, False to let
282 * it be handled by sendmail (or similar) or a string
283 * to completely override the Message-ID.
284 *
285 * If you are sending Email from a shell, be sure to set this value. As you
286 * could encounter delivery issues if you do not.
287 *
288 * @var mixed
289 * @access public
290 */
291 var $messageId = true;
292  
293 /**
294 * Temporary store of message header lines
295 *
296 * @var array
297 * @access private
298 */
299 var $__header = array();
300  
301 /**
302 * If set, boundary to use for multipart mime messages
303 *
304 * @var string
305 * @access private
306 */
307 var $__boundary = null;
308  
309 /**
310 * Temporary store of message lines
311 *
312 * @var array
313 * @access private
314 */
315 var $__message = array();
316  
317 /**
318 * Variable that holds SMTP connection
319 *
320 * @var resource
321 * @access private
322 */
323 var $__smtpConnection = null;
324  
325 /**
326 * Initialize component
327 *
328 * @param object $controller Instantiating controller
329 * @access public
330 */
331 function initialize(&$controller, $settings = array()) {
332 $this->Controller =& $controller;
333 if (Configure::read('App.encoding') !== null) {
334 $this->charset = Configure::read('App.encoding');
335 }
336 $this->_set($settings);
337 }
338  
339 /**
340 * Startup component
341 *
342 * @param object $controller Instantiating controller
343 * @access public
344 */
345 function startup(&$controller) {}
346  
347 /**
348 * Send an email using the specified content, template and layout
349 *
350 * @param mixed $content Either an array of text lines, or a string with contents
351 * If you are rendering a template this variable will be sent to the templates as `$content`
352 * @param string $template Template to use when sending email
353 * @param string $layout Layout to use to enclose email body
354 * @return boolean Success
355 * @access public
356 */
357 function send($content = null, $template = null, $layout = null) {
358 $this->_createHeader();
359  
360 if ($template) {
361 $this->template = $template;
362 }
363  
364 if ($layout) {
365 $this->layout = $layout;
366 }
367  
368 if (is_array($content)) {
369 $content = implode("\n", $content) . "\n";
370 }
371  
372 $this->htmlMessage = $this->textMessage = null;
373 if ($content) {
374 if ($this->sendAs === 'html') {
375 $this->htmlMessage = $content;
376 } elseif ($this->sendAs === 'text') {
377 $this->textMessage = $content;
378 } else {
379 $this->htmlMessage = $this->textMessage = $content;
380 }
381 }
382  
383 if ($this->sendAs === 'text') {
384 $message = $this->_wrap($content);
385 } else {
386 $message = $this->_wrap($content, 998);
387 }
388  
389 if ($this->template === null) {
390 $message = $this->_formatMessage($message);
391 } else {
392 $message = $this->_render($message);
393 }
394  
395 $message[] = '';
396 $this->__message = $message;
397  
398 if (!empty($this->attachments)) {
399 $this->_attachFiles();
400 }
401  
402 if (!is_null($this->__boundary)) {
403 $this->__message[] = '';
404 $this->__message[] = '--' . $this->__boundary . '--';
405 $this->__message[] = '';
406 }
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 * Reset all EmailComponent internal variables to be able to send out a new email.
420 *
421 * @access public
422 * @link http://book.cakephp.org/view/1285/Sending-Multiple-Emails-in-a-loop
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->subject = null;
433 $this->additionalParams = null;
434 $this->date = null;
435 $this->smtpError = null;
436 $this->attachments = array();
437 $this->htmlMessage = null;
438 $this->textMessage = null;
439 $this->messageId = true;
440 $this->__header = array();
441 $this->__boundary = null;
442 $this->__message = array();
443 }
444  
445 /**
446 * Render the contents using the current layout and template.
447 *
448 * @param string $content Content to render
449 * @return array Email ready to be sent
450 * @access private
451 */
452 function _render($content) {
453 $viewClass = $this->Controller->view;
454  
455 if ($viewClass != 'View') {
456 list($plugin, $viewClass) = pluginSplit($viewClass);
457 $viewClass = $viewClass . 'View';
458 App::import('View', $this->Controller->view);
459 }
460  
461 $View = new $viewClass($this->Controller);
462 $View->layout = $this->layout;
463 $msg = array();
464  
465 $content = implode("\n", $content);
466  
467 if ($this->sendAs === 'both') {
468 $htmlContent = $content;
469 if (!empty($this->attachments)) {
470 $msg[] = '--' . $this->__boundary;
471 $msg[] = 'Content-Type: multipart/alternative; boundary="alt-' . $this->__boundary . '"';
472 $msg[] = '';
473 }
474 $msg[] = '--alt-' . $this->__boundary;
475 $msg[] = 'Content-Type: text/plain; charset=' . $this->charset;
476 $msg[] = 'Content-Transfer-Encoding: 7bit';
477 $msg[] = '';
478  
479 $content = $View->element('email' . DS . 'text' . DS . $this->template, array('content' => $content), true);
480 $View->layoutPath = 'email' . DS . 'text';
481 $content = explode("\n", $this->textMessage = str_replace(array("\r\n", "\r"), "\n", $View->renderLayout($content)));
482  
483 $msg = array_merge($msg, $content);
484  
485 $msg[] = '';
486 $msg[] = '--alt-' . $this->__boundary;
487 $msg[] = 'Content-Type: text/html; charset=' . $this->charset;
488 $msg[] = 'Content-Transfer-Encoding: 7bit';
489 $msg[] = '';
490  
491 $htmlContent = $View->element('email' . DS . 'html' . DS . $this->template, array('content' => $htmlContent), true);
492 $View->layoutPath = 'email' . DS . 'html';
493 $htmlContent = explode("\n", $this->htmlMessage = str_replace(array("\r\n", "\r"), "\n", $View->renderLayout($htmlContent)));
494 $msg = array_merge($msg, $htmlContent);
495 $msg[] = '';
496 $msg[] = '--alt-' . $this->__boundary . '--';
497 $msg[] = '';
498  
499 ClassRegistry::removeObject('view');
500 return $msg;
501 }
502  
503 if (!empty($this->attachments)) {
504 if ($this->sendAs === 'html') {
505 $msg[] = '';
506 $msg[] = '--' . $this->__boundary;
507 $msg[] = 'Content-Type: text/html; charset=' . $this->charset;
508 $msg[] = 'Content-Transfer-Encoding: 7bit';
509 $msg[] = '';
510 } else {
511 $msg[] = '--' . $this->__boundary;
512 $msg[] = 'Content-Type: text/plain; charset=' . $this->charset;
513 $msg[] = 'Content-Transfer-Encoding: 7bit';
514 $msg[] = '';
515 }
516 }
517  
518 $content = $View->element('email' . DS . $this->sendAs . DS . $this->template, array('content' => $content), true);
519 $View->layoutPath = 'email' . DS . $this->sendAs;
520 $content = explode("\n", $rendered = str_replace(array("\r\n", "\r"), "\n", $View->renderLayout($content)));
521  
522 if ($this->sendAs === 'html') {
523 $this->htmlMessage = $rendered;
524 } else {
525 $this->textMessage = $rendered;
526 }
527  
528 $msg = array_merge($msg, $content);
529 ClassRegistry::removeObject('view');
530  
531 return $msg;
532 }
533  
534 /**
535 * Create unique boundary identifier
536 *
537 * @access private
538 */
539 function _createboundary() {
540 $this->__boundary = md5(uniqid(time()));
541 }
542  
543 /**
544 * Sets headers for the message
545 *
546 * @access public
547 * @param array Associative array containing headers to be set.
548 */
549 function header($headers) {
550 foreach ($headers as $header => $value) {
551 $this->__header[] = sprintf('%s: %s', trim($header), trim($value));
552 }
553 }
554 /**
555 * Create emails headers including (but not limited to) from email address, reply to,
556 * bcc and cc.
557 *
558 * @access private
559 */
560 function _createHeader() {
561 $headers = array();
562  
563 if ($this->delivery == 'smtp') {
564 if (is_array($this->to)) {
565 $headers['To'] = implode(', ', array_map(array($this, '_formatAddress'), $this->to));
566 } else {
567 $headers['To'] = $this->_formatAddress($this->to);
568 }
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'), $this->cc));
584 }
585  
586 if (!empty($this->bcc) && $this->delivery != 'smtp') {
587 $headers['Bcc'] = implode(', ', array_map(array($this, '_formatAddress'), $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'] = '<' . 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)) {
616 $this->_createBoundary();
617 $headers['MIME-Version'] = '1.0';
618 $headers['Content-Type'] = 'multipart/mixed; boundary="' . $this->__boundary . '"';
619 $headers[] = 'This part of the E-mail should never be seen. If';
620 $headers[] = 'you are reading this, consider upgrading your e-mail';
621 $headers[] = 'client to a MIME-compatible client.';
622 } elseif ($this->sendAs === 'text') {
623 $headers['Content-Type'] = 'text/plain; charset=' . $this->charset;
624 } elseif ($this->sendAs === 'html') {
625 $headers['Content-Type'] = 'text/html; charset=' . $this->charset;
626 } elseif ($this->sendAs === 'both') {
627 $headers['Content-Type'] = 'multipart/alternative; boundary="alt-' . $this->__boundary . '"';
628 }
629  
630 $headers['Content-Transfer-Encoding'] = '7bit';
631  
632 $this->header($headers);
633 }
634  
635 /**
636 * Format the message by seeing if it has attachments.
637 *
638 * @param string $message Message to format
639 * @access private
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 * Attach files by adding file contents inside boundaries.
660 *
661 * @access private
662 * @TODO: modify to use the core File class?
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 * Find the specified attachment in the list of file paths
694 *
695 * @param string $attachment Attachment file name to find
696 * @return string Path to located file
697 * @access private
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 * Wrap the message using EmailComponent::$lineLength
714 *
715 * @param string $message Message to wrap
716 * @param integer $lineLength Max length of line
717 * @return array Wrapped message
718 * @access protected
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 * Encode the specified string using the current charset
747 *
748 * @param string $subject String to encode
749 * @return string Encoded string
750 * @access private
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 * Format a string as an email address
773 *
774 * @param string $string String representing an email address
775 * @return string Email address suitable for email headers or smtp pipe
776 * @access private
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($matches[2]) . $this->_strip(' <' . $matches[3] . '>');
788 }
789 return $this->_strip($string);
790 }
791  
792 /**
793 * Remove certain elements (such as bcc:, to:, %0a) from given value.
794 * Helps prevent header injection / mainipulation on user content.
795 *
796 * @param string $value Value to strip
797 * @param boolean $message Set to true to indicate main message content
798 * @return string Stripped value
799 * @access private
800 */
801 function _strip($value, $message = false) {
802 $search = '%0a|%0d|Content-(?:Type|Transfer-Encoding)\:';
803 $search .= '|charset\=|mime-version\:|multipart/mixed|(?:[^a-z]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 * Wrapper for PHP mail function used for sending out emails
817 *
818 * @return bool Success
819 * @access private
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 * Helper method to get socket, overridden in tests
838 *
839 * @param array $config Config data for the socket.
840 * @return void
841 * @access protected
842 */
843 function _getSocket($config) {
844 $this->__smtpConnection =& new CakeSocket($config);
845 }
846  
847 /**
848 * Sends out email via SMTP
849 *
850 * @return bool Success
851 * @access private
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 * Protected method for sending data to SMTP connection
943 *
944 * @param string $data data to be sent to SMTP server
945 * @param mixed $checkCode code to check for in server response, false to skip
946 * @return bool Success
947 * @access protected
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 * Set as controller flash message a debug message showing current settings in component
979 *
980 * @return boolean Success
981 * @access private
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  
1010