AllowHtml = variable_get('smtp_allowhtml', 0); // Join the body array into one string. $message['body'] = implode("\n\n", $message['body']); if ($this->AllowHtml == 0) { // Convert any HTML to plain-text. $message['body'] = drupal_html_to_text($message['body']); // Wrap the mail body for sending. $message['body'] = drupal_wrap_mail($message['body']); } return $message; } /** * Send the e-mail message. * * @see drupal_mail() * * @param $message * A message array, as described in hook_mail_alter(). * @return * TRUE if the mail was successfully accepted, otherwise FALSE. */ public function mail(array $message) { if (variable_get('smtp_queue', FALSE) && (!isset($message['params']['skip_queue']) || !$message['params']['skip_queue'])) { smtp_send_queue($message); if (variable_get('smtp_debugging', SMTP_LOGGING_ERRORS) == SMTP_LOGGING_ALL) { watchdog('smtp', 'Queue sending mail to: @to', array('@to' => $message['to'])); } return TRUE; } else { return $this->mailWithoutQueue($message); } } public function mailWithoutQueue(array $message) { $to = $message['to']; $from = $message['from']; $body = $message['body']; $headers = $message['headers']; $subject = $message['subject']; // Optionally reroute all emails to a single address. $reroute_address = variable_get('smtp_reroute_address', ''); if (!empty($reroute_address)) { $to = $reroute_address; // Remove any CC and BCC headers that might have been set. unset($headers['cc']); unset($headers['bcc']); } // Create a new PHPMailer object - autoloaded from registry. $mailer = new PHPMailer(); $logging = variable_get('smtp_debugging', SMTP_LOGGING_ERRORS); // Turn on debugging, if requested. if ($logging == SMTP_LOGGING_ALL && user_access('administer smtp module')) { $mailer->SMTPDebug = TRUE; } // Set the from name. First we try to get the name from i18n, in the case // that it has been translated. The name is set according to the language // of the email being sent. $from_name = FALSE; if (function_exists('i18n_variable_get')) { // The 'language' value may be stored as an object. $langcode = $message['language']; if (is_object($langcode)) { $langcode = $langcode->language; } if (i18n_variable_get('smtp_fromname', $langcode, '') != '') { $from_name = i18n_variable_get('smtp_fromname', $langcode, ''); } else { // If value is not defined in settings, use site_name. $from_name = i18n_variable_get('site_name', $langcode, ''); } } if (variable_get('smtp_client_hostname', '') != '') { $mailer->Hostname = variable_get('smtp_client_hostname', ''); } if (variable_get('smtp_client_helo', '') != '') { $mailer->Helo = variable_get('smtp_client_helo', ''); } // If i18n is not enabled, we get the From Name through normal variables if (!$from_name) { if (variable_get('smtp_fromname', '') != '') { $from_name = variable_get('smtp_fromname', ''); } else { // If value is not defined in settings, use site_name. $from_name = variable_get('site_name', ''); } } //Hack to fix reply-to issue. if (!isset($headers['Reply-To']) || empty($headers['Reply-To'])) { if (strpos($from, '<')) { $reply = preg_replace('/>.*/', '', preg_replace('/.*_get_components($from); if (!valid_email_address($from_comp['email'])) { drupal_set_message(t('The submitted from address (@from) is not valid.', array('@from' => $from_comp['email'])), 'error'); if ($logging) { watchdog('smtp', 'The submitted from address (@from) is not valid.', array('@from' => $from_comp['email']), WATCHDOG_ERROR); } return FALSE; } // Defines the From value to what we expect. $mailer->From = $from_comp['email']; $mailer->FromName = empty($from_comp['name']) ? $from_name : $from_comp['name']; $mailer->Sender = $from_comp['email']; // Create the list of 'To:' recipients. $torecipients = explode(',', $to); foreach ($torecipients as $torecipient) { $to_comp = $this->_get_components($torecipient); $mailer->AddAddress($to_comp['email'], $to_comp['name']); } // Parse the headers of the message and set the PHPMailer object's settings // accordingly. foreach ($headers as $key => $value) { //watchdog('error', 'Key: ' . $key . ' Value: ' . $value); switch (drupal_strtolower($key)) { case 'from': if ($from == NULL or $from == '') { // If a from value was already given, then set based on header. // Should be the most common situation since drupal_mail moves the // from to headers. $from = $value; $mailer->From = $value; // then from can be out of sync with from_name ! $mailer->FromName = ''; $mailer->Sender = $value; } break; case 'content-type': // Parse several values on the Content-type header, storing them in an array like // key=value -> $vars['key']='value' $vars = explode(';', $value); foreach ($vars as $i => $var) { if ($cut = strpos($var, '=')) { $new_var = trim(drupal_strtolower(drupal_substr($var, $cut + 1))); $new_key = trim(drupal_substr($var, 0, $cut)); unset($vars[$i]); $vars[$new_key] = $new_var; } } // Set the charset based on the provided value, otherwise set it to UTF-8 (which is Drupals internal default). $mailer->CharSet = isset($vars['charset']) ? $vars['charset'] : 'UTF-8'; // If $vars is empty then set an empty value at index 0 to avoid a PHP warning in the next statement $vars[0] = isset($vars[0])?$vars[0]:''; switch ($vars[0]) { case 'text/plain': // The message includes only a plain text part. $mailer->IsHTML(FALSE); $content_type = 'text/plain'; break; case 'text/html': // The message includes only an HTML part. $mailer->IsHTML(TRUE); $content_type = 'text/html'; break; case 'multipart/related': // Get the boundary ID from the Content-Type header. $boundary = $this->_get_substring($value, 'boundary', '"', '"'); // The message includes an HTML part w/inline attachments. $mailer->ContentType = $content_type = 'multipart/related; boundary="' . $boundary . '"'; break; case 'multipart/alternative': // The message includes both a plain text and an HTML part. $mailer->ContentType = $content_type = 'multipart/alternative'; // Get the boundary ID from the Content-Type header. $boundary = $this->_get_substring($value, 'boundary', '"', '"'); break; case 'multipart/mixed': // The message includes one or more attachments. $mailer->ContentType = $content_type = 'multipart/mixed'; // Get the boundary ID from the Content-Type header. $boundary = $this->_get_substring($value, 'boundary', '"', '"'); break; default: // Everything else is unsuppored by PHPMailer. drupal_set_message(t('The %header of your message is not supported by PHPMailer and will be sent as text/plain instead.', array('%header' => "Content-Type: $value")), 'error'); if ($logging) { watchdog('smtp', 'The %header of your message is not supported by PHPMailer and will be sent as text/plain instead.', array('%header' => "Content-Type: $value"), WATCHDOG_ERROR); } // Force the Content-Type to be text/plain. $mailer->IsHTML(FALSE); $content_type = 'text/plain'; } break; case 'reply-to': // Only add a "reply-to" if it's not the same as "return-path". if ($value != $headers['Return-Path']) { $replyto_comp = $this->_get_components($value); $mailer->AddReplyTo($replyto_comp['email'], $replyto_comp['name']); } break; case 'content-transfer-encoding': $mailer->Encoding = $value; break; case 'return-path': $returnpath_comp = $this->_get_components($value); $mailer->Sender = $returnpath_comp['email']; break; case 'mime-version': case 'x-mailer': // Let PHPMailer specify these. break; case 'errors-to': $mailer->AddCustomHeader('Errors-To: ' . $value); break; case 'cc': $ccrecipients = explode(',', $value); foreach ($ccrecipients as $ccrecipient) { $cc_comp = $this->_get_components($ccrecipient); $mailer->AddCC($cc_comp['email'], $cc_comp['name']); } break; case 'bcc': $bccrecipients = explode(',', $value); foreach ($bccrecipients as $bccrecipient) { $bcc_comp = $this->_get_components($bccrecipient); $mailer->AddBCC($bcc_comp['email'], $bcc_comp['name']); } break; case 'message-id': $mailer->MessageID = $value; break; default: // The header key is not special - add it as is. $mailer->AddCustomHeader($key . ': ' . $value); } } /** * TODO * Need to figure out the following. * * Add one last header item, but not if it has already been added. * $errors_to = FALSE; * foreach ($mailer->CustomHeader as $custom_header) { * if ($custom_header[0] = '') { * $errors_to = TRUE; * } * } * if ($errors_to) { * $mailer->AddCustomHeader('Errors-To: '. $from); * } */ // Add the message's subject. $mailer->Subject = $subject; // Processes the message's body. switch ($content_type) { case 'multipart/related': $mailer->Body = $body; // TODO: Figure out if there is anything more to handling this type. break; case 'multipart/alternative': // Split the body based on the boundary ID. $body_parts = $this->_boundary_split($body, $boundary); foreach ($body_parts as $body_part) { // If plain/text within the body part, add it to $mailer->AltBody. if (strpos($body_part, 'text/plain')) { // Clean up the text. $body_part = trim($this->_remove_headers(trim($body_part))); // Include it as part of the mail object. $mailer->AltBody = $body_part; } // If plain/html within the body part, add it to $mailer->Body. elseif (strpos($body_part, 'text/html')) { // Clean up the text. $body_part = trim($this->_remove_headers(trim($body_part))); // Include it as part of the mail object. $mailer->Body = $body_part; } } break; case 'multipart/mixed': // Split the body based on the boundary ID. $body_parts = $this->_boundary_split($body, $boundary); // Determine if there is an HTML part for when adding the plain text part. $text_plain = FALSE; $text_html = FALSE; foreach ($body_parts as $body_part) { if (strpos($body_part, 'text/plain')) { $text_plain = TRUE; } if (strpos($body_part, 'text/html')) { $text_html = TRUE; } } foreach ($body_parts as $body_part) { // If test/plain within the body part, add it to either // $mailer->AltBody or $mailer->Body, depending on whether there is // also a text/html part ot not. if (strpos($body_part, 'multipart/alternative')) { // Get boundary ID from the Content-Type header. $boundary2 = $this->_get_substring($body_part, 'boundary', '"', '"'); // Clean up the text. $body_part = trim($this->_remove_headers(trim($body_part))); // Split the body based on the boundary ID. $body_parts2 = $this->_boundary_split($body_part, $boundary2); foreach ($body_parts2 as $body_part2) { // If plain/text within the body part, add it to $mailer->AltBody. if (strpos($body_part2, 'text/plain')) { // Clean up the text. $body_part2 = trim($this->_remove_headers(trim($body_part2))); // Include it as part of the mail object. $mailer->AltBody = $body_part2; $mailer->ContentType = 'multipart/mixed'; } // If plain/html within the body part, add it to $mailer->Body. elseif (strpos($body_part2, 'text/html')) { // Get the encoding. $body_part2_encoding = trim($this->_get_substring($body_part2, 'Content-Transfer-Encoding', ':', "\n")); // Clean up the text. $body_part2 = trim($this->_remove_headers(trim($body_part2))); // Check whether the encoding is base64, and if so, decode it. if (drupal_strtolower($body_part2_encoding) == 'base64') { // Include it as part of the mail object. $mailer->Body = base64_decode($body_part2); // Ensure the whole message is recoded in the base64 format. $mailer->Encoding = 'base64'; } else { // Include it as part of the mail object. $mailer->Body = $body_part2; } $mailer->ContentType = 'multipart/mixed'; } } } // If text/plain within the body part, add it to $mailer->Body. elseif (strpos($body_part, 'text/plain')) { // Clean up the text. $body_part = trim($this->_remove_headers(trim($body_part))); if ($text_html) { $mailer->AltBody = $body_part; $mailer->IsHTML(TRUE); $mailer->ContentType = 'multipart/mixed'; } else { $mailer->Body = $body_part; $mailer->IsHTML(FALSE); $mailer->ContentType = 'multipart/mixed'; } } // If text/html within the body part, add it to $mailer->Body. elseif (strpos($body_part, 'text/html')) { // Clean up the text. $body_part = trim($this->_remove_headers(trim($body_part))); // Include it as part of the mail object. $mailer->Body = $body_part; $mailer->IsHTML(TRUE); $mailer->ContentType = 'multipart/mixed'; } // Add the attachment. elseif (strpos($body_part, 'Content-Disposition: attachment;') && !isset($message['params']['attachments'])) { $file_path = $this->_get_substring($body_part, 'filename=', '"', '"'); $file_name = $this->_get_substring($body_part, ' name=', '"', '"'); $file_encoding = $this->_get_substring($body_part, 'Content-Transfer-Encoding', ' ', "\n"); $file_type = $this->_get_substring($body_part, 'Content-Type', ' ', ';'); if (file_exists($file_path)) { if (!$mailer->AddAttachment($file_path, $file_name, $file_encoding, $file_type)) { drupal_set_message(t('Attahment could not be found or accessed.')); } } else { // Clean up the text. $body_part = trim($this->_remove_headers(trim($body_part))); if (drupal_strtolower($file_encoding) == 'base64') { $attachment = base64_decode($body_part); } elseif (drupal_strtolower($file_encoding) == 'quoted-printable') { $attachment = quoted_printable_decode($body_part); } else { $attachment = $body_part; } $attachment_new_filename = drupal_tempnam('temporary://', 'smtp'); $file_path = file_save_data($attachment, $attachment_new_filename, FILE_EXISTS_REPLACE); $real_path = drupal_realpath($file_path->uri); if (!$mailer->AddAttachment($real_path, $file_name)) { drupal_set_message(t('Attachment could not be found or accessed.')); } } } } break; default: $mailer->Body = $body; break; } // Process mimemail attachments, which are prepared in mimemail_mail(). if (isset($message['params']['attachments'])) { foreach ($message['params']['attachments'] as $attachment) { if (isset($attachment['filecontent'])) { $mailer->AddStringAttachment($attachment['filecontent'], $attachment['filename'], 'base64', $attachment['filemime']); } if (isset($attachment['filepath'])) { $filename = isset($attachment['filename']) ? $attachment['filename'] : basename($attachment['filepath']); $filemime = isset($attachment['filemime']) ? $attachment['filemime'] : file_get_mimetype($attachment['filepath']); $mailer->AddAttachment($attachment['filepath'], $filename, 'base64', $filemime); } } } // Set the authentication settings. $username = variable_get('smtp_username', ''); $password = variable_get('smtp_password', ''); // If username and password are given, use SMTP authentication. if ($username != '' && $password != '') { $mailer->SMTPAuth = TRUE; $mailer->Username = $username; $mailer->Password = $password; } // Set the protocol prefix for the smtp host. switch (variable_get('smtp_protocol', 'standard')) { case 'ssl': $mailer->SMTPSecure = 'ssl'; break; case 'tls': $mailer->SMTPSecure = 'tls'; break; default: $mailer->SMTPSecure = ''; } // Set other connection settings. $mailer->Host = variable_get('smtp_host', '') . ';' . variable_get('smtp_hostbackup', ''); $mailer->Port = variable_get('smtp_port', '25'); $mailer->Mailer = 'smtp'; // Integration with the Maillog module. if (module_exists('maillog')) { if (variable_get('maillog_log', TRUE)) { $record = new stdClass; // In case the subject/from/to is already encoded, decode with // mime_header_decode. $record->header_message_id = isset($mailer->MessageID) ? $mailer->MessageID : NULL; $record->subject = drupal_substr(mime_header_decode($mailer->Subject), 0, 255); $record->header_from = $from; $record->header_to = $to; $record->header_reply_to = isset($headers['Reply-To']) ? $headers['Reply-To'] : ''; $record->header_all = serialize($headers); $record->sent_date = REQUEST_TIME; // Used to separate different portions of the body string. $divider = str_repeat('-', 60) . "\n"; // Load the attachments. $attachments = $mailer->GetAttachments(); $record->body = ''; // If there's more than one item to display, add a divider. if (!empty($mailer->AltBody) || !empty($attachments)) { $record->body .= t('Body') . ":\n"; $record->body .= $divider; } // Add the body field. if (isset($mailer->Body)) { $record->body .= $mailer->Body; } else { $record->body .= t('*No message body*') . ":\n"; } // The AltBody value is optional. if (!empty($mailer->AltBody)) { $record->body .= "\n"; $record->body .= $divider; $record->body .= t('Alternative body') . ":\n"; $record->body .= $divider; $record->body .= $mailer->AltBody; } // List the attachments. if (!empty($attachments)) { $record->body .= "\n"; $record->body .= $divider; $record->body .= t('Attachments') . ":\n"; $record->body .= $divider; foreach ($attachments as $file) { $record->body .= t('Filename') . ':' . $file[1] . "\n"; $record->body .= t('Name') . ':' . $file[2] . "\n"; $record->body .= t('Encoding') . ':' . $file[3] . "\n"; $record->body .= t('Type') . ':' . $file[4] . "\n"; $record->body .= "\n"; } } drupal_write_record('maillog', $record); } // Display the e-mail using Devel module. if (variable_get('maillog_devel', TRUE) && function_exists('dpm')) { $devel_msg = array(); $devel_msg[t('Subject')] = $mailer->Subject; $devel_msg[t('From')] = $from; $devel_msg[t('To')] = $to; $devel_msg[t('Reply-To')] = isset($headers['Reply-To']) ? $headers['Reply-To'] : NULL; $devel_msg[t('Headers')] = $headers; $devel_msg[t('Body')] = $mailer->Body; $devel_msg[t('Alternative body')] = $mailer->AltBody; $devel_msg[t('Attachments')] = $mailer->GetAttachments(); dpm($devel_msg, 'maillog'); } } $error = FALSE; // Email delivery was disabled. if (!variable_get('smtp_deliver', TRUE)) { if ($logging) { $params = array( '@from' => $from, '@to' => $to, ); watchdog('smtp', 'Email delivery is disabled, did not send email from @from to @to.', $params); } } else { if (!$mailer->send()) { $params = array( '@from' => $from, '@to' => $to, '!error_message' => $mailer->ErrorInfo ); if (variable_get('smtp_queue_fail', FALSE)) { if ($logging) { watchdog('smtp', 'Error sending e-mail from @from to @to, will retry on cron run : !error_message.', $params, WATCHDOG_ERROR); } smtp_failed_messages($message); } elseif ($logging) { $error = TRUE; watchdog('smtp', 'Error sending e-mail from @from to @to : !error_message', $params, WATCHDOG_ERROR); } } elseif (variable_get('smtp_debugging', SMTP_LOGGING_ERRORS) == SMTP_LOGGING_ALL) { watchdog('smtp', 'Sent mail to: @to', array('@to' => $to)); } } $mailer->SmtpClose(); return !$error; } /** * Splits the input into parts based on the given boundary. * * Swiped from Mail::MimeDecode, with modifications based on Drupal's coding * standards and this bug report: http://pear.php.net/bugs/bug.php?id=6495 * * @param input * A string containing the body text to parse. * @param boundary * A string with the boundary string to parse on. * @return * An array containing the resulting mime parts */ protected function _boundary_split($input, $boundary) { $parts = array(); $bs_possible = drupal_substr($boundary, 2, -2); $bs_check = '\"' . $bs_possible . '\"'; if ($boundary == $bs_check) { $boundary = $bs_possible; } $tmp = explode('--' . $boundary, $input); for ($i = 1; $i < count($tmp); $i++) { if (trim($tmp[$i])) { $parts[] = $tmp[$i]; } } return $parts; } // End of _smtp_boundary_split(). /** * Strips the headers from the body part. * * @param input * A string containing the body part to strip. * @return * A string with the stripped body part. */ protected function _remove_headers($input) { $part_array = explode("\n", $input); // will strip these headers according to RFC2045 $headers_to_strip = array( 'Content-Type', 'Content-Transfer-Encoding', 'Content-ID', 'Content-Disposition'); $pattern = '/^(' . implode('|', $headers_to_strip) . '):/'; while (count($part_array) > 0) { // ignore trailing spaces/newlines $line = rtrim($part_array[0]); // if the line starts with a known header string if (preg_match($pattern, $line)) { $line = rtrim(array_shift($part_array)); // remove line containing matched header. // if line ends in a ';' and the next line starts with four spaces, it's a continuation // of the header split onto the next line. Continue removing lines while we have this condition. while (substr($line, -1) == ';' && count($part_array) > 0 && substr($part_array[0], 0, 4) == ' ') { $line = rtrim(array_shift($part_array)); } } else { // no match header, must be past headers; stop searching. break; } } $output = implode("\n", $part_array); return $output; } // End of _smtp_remove_headers(). /** * Returns a string that is contained within another string. * * Returns the string from within $source that is some where after $target * and is between $beginning_character and $ending_character. * * @param $source * A string containing the text to look through. * @param $target * A string containing the text in $source to start looking from. * @param $beginning_character * A string containing the character just before the sought after text. * @param $ending_character * A string containing the character just after the sought after text. * @return * A string with the text found between the $beginning_character and the * $ending_character. */ protected function _get_substring($source, $target, $beginning_character, $ending_character) { $search_start = strpos($source, $target) + 1; $first_character = strpos($source, $beginning_character, $search_start) + 1; $second_character = strpos($source, $ending_character, $first_character) + 1; $substring = drupal_substr($source, $first_character, $second_character - $first_character); $string_length = drupal_strlen($substring) - 1; if ($substring[$string_length] == $ending_character) { $substring = drupal_substr($substring, 0, $string_length); } return trim($substring); } // End of _smtp_get_substring(). /** * Returns an array of name and email address from a string. * * @param $input * A string that contains different possible combinations of names and * email address. * @return * An array containing a name and an email address. */ protected function _get_components($input) { $components = array( 'input' => $input, 'name' => '', 'email' => '', ); // If the input is a valid email address in its entirety, then there is // nothing to do, just return that. if (valid_email_address($input)) { $components['email'] = trim($input); return $components; } // Check if $input has one of the following formats, extract what we can: // some name // "another name" // if (preg_match('/^"?([^"\t\n]*)"?\s*<([^>\t\n]*)>$/', $input, $matches)) { $components['name'] = trim($matches[1]); $components['email'] = trim($matches[2]); } return $components; } }