You are here

public function SmtpMailSystem::mailWithoutQueue in SMTP Authentication Support 7

Same name and namespace in other branches
  1. 7.2 smtp.mail.inc \SmtpMailSystem::mailWithoutQueue()
1 call to SmtpMailSystem::mailWithoutQueue()
SmtpMailSystem::mail in ./smtp.mail.inc
Send the e-mail message.

File

./smtp.mail.inc, line 62
The code processing mail in the smtp module.

Class

SmtpMailSystem
Modify the drupal mail system to use smtp when sending emails. Include the option to choose between plain text or HTML

Code

public function mailWithoutQueue(array $message) {
  $to = $message['to'];
  $from = $message['from'];
  $from_name = isset($message['from_name']) ? $message['from_name'] : FALSE;
  $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.
  if (empty($from_name)) {
    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 {
        $from_name = i18n_variable_get('site_name', $langcode, '');
      }
    }
    else {
      if (variable_get('smtp_fromname', '') != '') {
        $from_name = variable_get('smtp_fromname', '');
      }
      else {
        $from_name = variable_get('site_name', '');
      }
    }
  }
  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', '');
  }

  //Hack to fix reply-to issue.
  if (!isset($headers['Reply-To']) || empty($headers['Reply-To'])) {
    if (strpos($from, '<')) {
      $reply = preg_replace('/>.*/', '', preg_replace('/.*</', '', $from));
    }
    else {
      $reply = $from;
    }
    $headers['Reply-To'] = $reply;
  }
  $properfrom = variable_get('smtp_from', '');
  if (!empty($properfrom)) {
    $headers['From'] = $properfrom;
    $from = $properfrom;
  }
  if ($from == NULL || $from == '') {

    // If from e-mail address is blank, use smtp_from config option.
    if (($from = variable_get('smtp_from', '')) == '') {

      // If smtp_from config option is blank, use site_email.
      if (($from = variable_get('site_mail', '')) == '') {
        drupal_set_message(t('There is no submitted from address.'), 'error');
        if ($logging) {
          watchdog('smtp', 'There is no submitted from address.', array(), WATCHDOG_ERROR);
        }
        return FALSE;
      }
    }
  }
  $from_comp = $this
    ->_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':
        $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;
        }
        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';
            }
            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';
            }
          }
        }
        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';
          }
        }
        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';
        }
        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']) && isset($attachment['uri'])) {
        $attachment['filepath'] = $attachment['uri'];
      }
      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);
      }
    }
  }

  // Process inline images.
  if (!empty($message['params']['images'])) {
    foreach ($message['params']['images'] as $image) {
      if (!empty($image['filepath']) && !empty($image['cid'])) {
        if (file_exists($image['filepath'])) {
          $image_extension = pathinfo($image['filepath'], PATHINFO_EXTENSION);
          $image_mime_type = PHPMailer::_mime_types($image_extension);
          if (empty($image['name'])) {
            $image_name = pathinfo($image['filepath'], PATHINFO_FILENAME);
          }
          else {
            $image_name = $image['name'];
          }
          $mailer
            ->AddEmbeddedImage($image['filepath'], $image['cid'], $image_name, 'base64', $image_mime_type);
        }
      }
    }
  }

  // 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 SSL stream context options
  if (variable_get('smtp_protocol', 'standard') != 'standard') {
    $smtp_allow_self_signed = array(
      'allow_self_signed' => variable_get('smtp_allow_self_signed', FALSE),
    );
    if (version_compare(phpversion(), '5.6.0', '>=')) {
      $smtp_verify_peer = array(
        'verify_peer' => variable_get('smtp_verify_peer', TRUE),
      );
      $smtp_verify_peer_name = array(
        'verify_peer_name' => variable_get('smtp_verify_peer_name', TRUE),
      );
      $mailer->SMTPOptions = array(
        'ssl' => array_merge($smtp_verify_peer, $smtp_verify_peer_name, $smtp_allow_self_signed),
      );
    }
    else {
      $smtp_verify_peer = array(
        'verify_peer' => variable_get('smtp_verify_peer', FALSE),
      );
      $mailer->SMTPOptions = array(
        'ssl' => array_merge($smtp_verify_peer, $smtp_allow_self_signed),
      );
    }
  }

  // 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;
}