Controller = $collection->getController(); parent::__construct($collection, $settings); } /** * Initialize component * * @param object $controller Instantiating controller */ public function initialize(&$controller) { if (Configure::read('App.encoding') !== null) { $this->charset = Configure::read('App.encoding'); } } /** * Startup component * * @param object $controller Instantiating controller */ public function startup(&$controller) {} /** * Send an email using the specified content, template and layout * * @param mixed $content Either an array of text lines, or a string with contents * If you are rendering a template this variable will be sent to the templates as `$content` * @param string $template Template to use when sending email * @param string $layout Layout to use to enclose email body * @return boolean Success */ public function send($content = null, $template = null, $layout = null) { $this->_createHeader(); if ($template) { $this->template = $template; } if ($layout) { $this->layout = $layout; } if (is_array($content)) { $content = implode("\n", $content) . "\n"; } $this->htmlMessage = $this->textMessage = null; if ($content) { if ($this->sendAs === 'html') { $this->htmlMessage = $content; } elseif ($this->sendAs === 'text') { $this->textMessage = $content; } else { $this->htmlMessage = $this->textMessage = $content; } } if ($this->sendAs === 'text') { $message = $this->_wrap($content); } else { $message = $this->_wrap($content, 998); } if ($this->template === null) { $message = $this->_formatMessage($message); } else { $message = $this->_render($message); } $message[] = ''; $this->_message = $message; if (!empty($this->attachments)) { $this->_attachFiles(); } if (!is_null($this->_boundary)) { $this->_message[] = ''; $this->_message[] = '--' . $this->_boundary . '--'; $this->_message[] = ''; } $_method = '_' . $this->delivery; $sent = $this->$_method(); $this->_header = array(); $this->_message = array(); return $sent; } /** * Reset all EmailComponent internal variables to be able to send out a new email. * * @link http://book.cakephp.org/view/1285/Sending-Multiple-Emails-in-a-loop */ public function reset() { $this->template = null; $this->to = array(); $this->from = null; $this->replyTo = null; $this->return = null; $this->cc = array(); $this->bcc = array(); $this->subject = null; $this->additionalParams = null; $this->smtpError = null; $this->attachments = array(); $this->htmlMessage = null; $this->textMessage = null; $this->messageId = true; $this->_header = array(); $this->_boundary = null; $this->_message = array(); } /** * Render the contents using the current layout and template. * * @param string $content Content to render * @return array Email ready to be sent * @access private */ function _render($content) { $viewClass = $this->Controller->view; if ($viewClass != 'View') { list($plugin, $viewClass) = pluginSplit($viewClass); $viewClass = $viewClass . 'View'; App::import('View', $this->Controller->view); } $View = new $viewClass($this->Controller, false); $View->layout = $this->layout; $msg = array(); $content = implode("\n", $content); if ($this->sendAs === 'both') { $htmlContent = $content; if (!empty($this->attachments)) { $msg[] = '--' . $this->_boundary; $msg[] = 'Content-Type: multipart/alternative; boundary="alt-' . $this->_boundary . '"'; $msg[] = ''; } $msg[] = '--alt-' . $this->_boundary; $msg[] = 'Content-Type: text/plain; charset=' . $this->charset; $msg[] = 'Content-Transfer-Encoding: 7bit'; $msg[] = ''; $content = $View->element('email' . DS . 'text' . DS . $this->template, array('content' => $content), true); $View->layoutPath = 'email' . DS . 'text'; $content = explode("\n", $this->textMessage = str_replace(array("\r\n", "\r"), "\n", $View->renderLayout($content))); $msg = array_merge($msg, $content); $msg[] = ''; $msg[] = '--alt-' . $this->_boundary; $msg[] = 'Content-Type: text/html; charset=' . $this->charset; $msg[] = 'Content-Transfer-Encoding: 7bit'; $msg[] = ''; $htmlContent = $View->element('email' . DS . 'html' . DS . $this->template, array('content' => $htmlContent), true); $View->layoutPath = 'email' . DS . 'html'; $htmlContent = explode("\n", $this->htmlMessage = str_replace(array("\r\n", "\r"), "\n", $View->renderLayout($htmlContent))); $msg = array_merge($msg, $htmlContent); $msg[] = ''; $msg[] = '--alt-' . $this->_boundary . '--'; $msg[] = ''; return $msg; } if (!empty($this->attachments)) { if ($this->sendAs === 'html') { $msg[] = ''; $msg[] = '--' . $this->_boundary; $msg[] = 'Content-Type: text/html; charset=' . $this->charset; $msg[] = 'Content-Transfer-Encoding: 7bit'; $msg[] = ''; } else { $msg[] = '--' . $this->_boundary; $msg[] = 'Content-Type: text/plain; charset=' . $this->charset; $msg[] = 'Content-Transfer-Encoding: 7bit'; $msg[] = ''; } } $content = $View->element('email' . DS . $this->sendAs . DS . $this->template, array('content' => $content), true); $View->layoutPath = 'email' . DS . $this->sendAs; $content = explode("\n", $rendered = str_replace(array("\r\n", "\r"), "\n", $View->renderLayout($content))); if ($this->sendAs === 'html') { $this->htmlMessage = $rendered; } else { $this->textMessage = $rendered; } $msg = array_merge($msg, $content); return $msg; } /** * Create unique boundary identifier * * @access private */ function _createboundary() { $this->_boundary = md5(uniqid(time())); } /** * Sets headers for the message * * @access public * @param array Associative array containing headers to be set. */ function header($headers) { foreach ($headers as $header => $value) { $this->_header[] = sprintf('%s: %s', trim($header), trim($value)); } } /** * Create emails headers including (but not limited to) from email address, reply to, * bcc and cc. * * @access private */ function _createHeader() { $headers = array(); if ($this->delivery == 'smtp') { if (is_array($this->to)) { $headers['To'] = implode(', ', array_map(array($this, '_formatAddress'), $this->to)); } else { $headers['To'] = $this->_formatAddress($this->to); } } $headers['From'] = $this->_formatAddress($this->from); if (!empty($this->replyTo)) { $headers['Reply-To'] = $this->_formatAddress($this->replyTo); } if (!empty($this->return)) { $headers['Return-Path'] = $this->_formatAddress($this->return); } if (!empty($this->readReceipt)) { $headers['Disposition-Notification-To'] = $this->_formatAddress($this->readReceipt); } if (!empty($this->cc)) { $headers['cc'] = implode(', ', array_map(array($this, '_formatAddress'), $this->cc)); } if (!empty($this->bcc) && $this->delivery != 'smtp') { $headers['Bcc'] = implode(', ', array_map(array($this, '_formatAddress'), $this->bcc)); } if ($this->delivery == 'smtp') { $headers['Subject'] = $this->_encode($this->subject); } if ($this->messageId !== false) { if ($this->messageId === true) { $headers['Message-ID'] = '<' . String::UUID() . '@' . env('HTTP_HOST') . '>'; } else { $headers['Message-ID'] = $this->messageId; } } $headers['X-Mailer'] = $this->xMailer; if (!empty($this->headers)) { foreach ($this->headers as $key => $val) { $headers['X-' . $key] = $val; } } if (!empty($this->attachments)) { $this->_createBoundary(); $headers['MIME-Version'] = '1.0'; $headers['Content-Type'] = 'multipart/mixed; boundary="' . $this->_boundary . '"'; $headers[] = 'This part of the E-mail should never be seen. If'; $headers[] = 'you are reading this, consider upgrading your e-mail'; $headers[] = 'client to a MIME-compatible client.'; } elseif ($this->sendAs === 'text') { $headers['Content-Type'] = 'text/plain; charset=' . $this->charset; } elseif ($this->sendAs === 'html') { $headers['Content-Type'] = 'text/html; charset=' . $this->charset; } elseif ($this->sendAs === 'both') { $headers['Content-Type'] = 'multipart/alternative; boundary="alt-' . $this->_boundary . '"'; } $headers['Content-Transfer-Encoding'] = '7bit'; $this->header($headers); } /** * Format the message by seeing if it has attachments. * * @param string $message Message to format * @access private */ function _formatMessage($message) { if (!empty($this->attachments)) { $prefix = array('--' . $this->_boundary); if ($this->sendAs === 'text') { $prefix[] = 'Content-Type: text/plain; charset=' . $this->charset; } elseif ($this->sendAs === 'html') { $prefix[] = 'Content-Type: text/html; charset=' . $this->charset; } elseif ($this->sendAs === 'both') { $prefix[] = 'Content-Type: multipart/alternative; boundary="alt-' . $this->_boundary . '"'; } $prefix[] = 'Content-Transfer-Encoding: 7bit'; $prefix[] = ''; $message = array_merge($prefix, $message); } return $message; } /** * Attach files by adding file contents inside boundaries. * * @access private * @TODO: modify to use the core File class? */ function _attachFiles() { $files = array(); foreach ($this->attachments as $filename => $attachment) { $file = $this->_findFiles($attachment); if (!empty($file)) { if (is_int($filename)) { $filename = basename($file); } $files[$filename] = $file; } } foreach ($files as $filename => $file) { $handle = fopen($file, 'rb'); $data = fread($handle, filesize($file)); $data = chunk_split(base64_encode($data)) ; fclose($handle); $this->_message[] = '--' . $this->_boundary; $this->_message[] = 'Content-Type: application/octet-stream'; $this->_message[] = 'Content-Transfer-Encoding: base64'; $this->_message[] = 'Content-Disposition: attachment; filename="' . basename($filename) . '"'; $this->_message[] = ''; $this->_message[] = $data; $this->_message[] = ''; } } /** * Find the specified attachment in the list of file paths * * @param string $attachment Attachment file name to find * @return string Path to located file * @access private */ function _findFiles($attachment) { if (file_exists($attachment)) { return $attachment; } foreach ($this->filePaths as $path) { if (file_exists($path . DS . $attachment)) { $file = $path . DS . $attachment; return $file; } } return null; } /** * Wrap the message using EmailComponent::$lineLength * * @param string $message Message to wrap * @param integer $lineLength Max length of line * @return array Wrapped message * @access protected */ function _wrap($message, $lineLength = null) { $message = $this->_strip($message, true); $message = str_replace(array("\r\n","\r"), "\n", $message); $lines = explode("\n", $message); $formatted = array(); if ($this->_lineLength !== null) { trigger_error(__('_lineLength cannot be accessed please use lineLength'), E_USER_WARNING); $this->lineLength = $this->_lineLength; } if (!$lineLength) { $lineLength = $this->lineLength; } foreach ($lines as $line) { if (substr($line, 0, 1) == '.') { $line = '.' . $line; } $formatted = array_merge($formatted, explode("\n", wordwrap($line, $lineLength, "\n", true))); } $formatted[] = ''; return $formatted; } /** * Encode the specified string using the current charset * * @param string $subject String to encode * @return string Encoded string * @access private */ function _encode($subject) { $subject = $this->_strip($subject); $nl = "\r\n"; if ($this->delivery == 'mail') { $nl = ''; } $internalEncoding = function_exists('mb_internal_encoding'); if ($internalEncoding) { $restore = mb_internal_encoding(); mb_internal_encoding($this->charset); } $return = mb_encode_mimeheader($subject, $this->charset, 'B', $nl); if ($internalEncoding) { mb_internal_encoding($restore); } return $return; } /** * Format a string as an email address * * @param string $string String representing an email address * @return string Email address suitable for email headers or smtp pipe * @access private */ function _formatAddress($string, $smtp = false) { $hasAlias = preg_match('/((.*)\s)?<(.+)>/', $string, $matches); if ($smtp && $hasAlias) { return $this->_strip('<' . $matches[3] . '>'); } elseif ($smtp) { return $this->_strip('<' . $string . '>'); } if ($hasAlias && !empty($matches[2])) { return $this->_strip($matches[2] . ' <' . $matches[3] . '>'); } return $this->_strip($string); } /** * Remove certain elements (such as bcc:, to:, %0a) from given value. * Helps prevent header injection / mainipulation on user content. * * @param string $value Value to strip * @param boolean $message Set to true to indicate main message content * @return string Stripped value * @access private */ function _strip($value, $message = false) { $search = '%0a|%0d|Content-(?:Type|Transfer-Encoding)\:'; $search .= '|charset\=|mime-version\:|multipart/mixed|(?:[^a-z]to|b?cc)\:.*'; if ($message !== true) { $search .= '|\r|\n'; } $search = '#(?:' . $search . ')#i'; while (preg_match($search, $value)) { $value = preg_replace($search, '', $value); } return $value; } /** * Wrapper for PHP mail function used for sending out emails * * @return bool Success * @access private */ function _mail() { $header = implode("\r\n", $this->_header); $message = implode("\r\n", $this->_message); if (is_array($this->to)) { $to = implode(', ', array_map(array($this, '_formatAddress'), $this->to)); } else { $to = $this->to; } if (ini_get('safe_mode')) { return @mail($to, $this->_encode($this->subject), $message, $header); } return @mail($to, $this->_encode($this->subject), $message, $header, $this->additionalParams); } /** * Sends out email via SMTP * * @return bool Success * @access private */ function _smtp() { App::import('Core', 'CakeSocket'); $defaults = array( 'host' => 'localhost', 'port' => 25, 'protocol' => 'smtp', 'timeout' => 30 ); $this->smtpOptions = array_merge($defaults, $this->smtpOptions); $this->_smtpConnection = new CakeSocket($this->smtpOptions); if (!$this->_smtpConnection->connect()) { $this->smtpError = $this->_smtpConnection->lastError(); return false; } elseif (!$this->_smtpSend(null, '220')) { return false; } $httpHost = env('HTTP_HOST'); if (isset($this->smtpOptions['client'])) { $host = $this->smtpOptions['client']; } elseif (!empty($httpHost)) { $host = $httpHost; } else { $host = 'localhost'; } if (!$this->_smtpSend("EHLO {$host}", '250') && !$this->_smtpSend("HELO {$host}", '250')) { return false; } if (isset($this->smtpOptions['username']) && isset($this->smtpOptions['password'])) { $authRequired = $this->_smtpSend('AUTH LOGIN', '334|503'); if ($authRequired == '334') { if (!$this->_smtpSend(base64_encode($this->smtpOptions['username']), '334')) { return false; } if (!$this->_smtpSend(base64_encode($this->smtpOptions['password']), '235')) { return false; } } elseif ($authRequired != '503') { return false; } } if (!$this->_smtpSend('MAIL FROM: ' . $this->_formatAddress($this->from, true))) { return false; } if (!is_array($this->to)) { $tos = array($this->to); } else { $tos = $this->to; } foreach ($tos as $to) { if (!$this->_smtpSend('RCPT TO: ' . $this->_formatAddress($to, true))) { return false; } } foreach ($this->cc as $cc) { if (!$this->_smtpSend('RCPT TO: ' . $this->_formatAddress($cc, true))) { return false; } } foreach ($this->bcc as $bcc) { if (!$this->_smtpSend('RCPT TO: ' . $this->_formatAddress($bcc, true))) { return false; } } if (!$this->_smtpSend('DATA', '354')) { return false; } $header = implode("\r\n", $this->_header); $message = implode("\r\n", $this->_message); if (!$this->_smtpSend($header . "\r\n\r\n" . $message . "\r\n\r\n\r\n.")) { return false; } $this->_smtpSend('QUIT', false); $this->_smtpConnection->disconnect(); return true; } /** * Protected method for sending data to SMTP connection * * @param string $data data to be sent to SMTP server * @param mixed $checkCode code to check for in server response, false to skip * @return bool Success * @access protected */ function _smtpSend($data, $checkCode = '250') { if (!is_null($data)) { $this->_smtpConnection->write($data . "\r\n"); } while ($checkCode !== false) { $response = ''; $startTime = time(); while (substr($response, -2) !== "\r\n" && ((time() - $startTime) < $this->smtpOptions['timeout'])) { $response .= $this->_smtpConnection->read(); } if (substr($response, -2) !== "\r\n") { $this->smtpError = 'timeout'; return false; } $response = end(explode("\r\n", rtrim($response, "\r\n"))); if (preg_match('/^(' . $checkCode . ')(.)/', $response, $code)) { if ($code[2] === '-') { continue; } return $code[1]; } $this->smtpError = $response; return false; } return true; } /** * Set as controller flash message a debug message showing current settings in component * * @return boolean Success * @access private */ function _debug() { $nl = "\n"; $header = implode($nl, $this->_header); $message = implode($nl, $this->_message); $fm = '
'; if (is_array($this->to)) { $to = implode(', ', array_map(array($this, '_formatAddress'), $this->to)); } else { $to = $this->to; } $fm .= sprintf('%s %s%s', 'To:', $to, $nl); $fm .= sprintf('%s %s%s', 'From:', $this->from, $nl); $fm .= sprintf('%s %s%s', 'Subject:', $this->_encode($this->subject), $nl); $fm .= sprintf('%s%3$s%3$s%s', 'Header:', $header, $nl); $fm .= sprintf('%s%3$s%3$s%s', 'Parameters:', $this->additionalParams, $nl); $fm .= sprintf('%s%3$s%3$s%s', 'Message:', $message, $nl); $fm .= ''; if (isset($this->Controller->Session)) { $this->Controller->Session->setFlash($fm, 'default', null, 'email'); return true; } return $fm; } }