You are here

class Messaging_HTML_MailSystem in Messaging 7

Sendgrid mail system

We use HTML formatting + we need to take care of properly filtering the headers.

The e-mail is sent through default PHP mail though, Sendgrid config should be in the backend

For Exim configuration see http://wiki.sendgrid.com/doku.php?id=exim4

@todo Explore other options for sending, like PHPMailer or

Hierarchy

Expanded class hierarchy of Messaging_HTML_MailSystem

File

messaging_htmlmail/messaging_htmlmail.inc, line 98
Drupal Messaging Framework - Send_Method class file

View source
class Messaging_HTML_MailSystem extends DefaultMailSystem {

  /**
   * Concatenate and wrap the e-mail body for plain-text mails.
   *
   * @param $message
   *   A message array, as described in hook_mail_alter().
   *
   * @return
   *   The formatted $message.
   */
  public function format(array $mail) {

    // Note we use two templates here
    // - messaging_template_body_html, for the body content
    // - messaging_htmlmail, for formatting the mail body
    $body = drupal_render($mail['body']);
    $mail['body'] = theme('messaging_htmlmail', array(
      'body' => $body,
      'element' => $mail['body'],
    ));
    $mimemail = $this
      ->mimemail_html_body($mail['body'], $mail['subject'], $mail['body_plain'], $mail['attachments']);

    // Merge mimemail headers on top of regular headers
    $mail['headers'] = array_merge($mail['headers'], $mimemail['headers']);
    $mail['body'] = $mimemail['body'];
    return $mail;
  }

  /**
   * Send an e-mail message, using Drupal variables and default settings.
   *
   * @see http://php.net/manual/en/function.mail.php
   * @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 'Return-Path' isn't already set in php.ini, we pass it separately
    // as an additional parameter instead of in the header.
    // However, if PHP's 'safe_mode' is on, this is not allowed.
    if (isset($message['headers']['Return-Path']) && !ini_get('safe_mode')) {
      $return_path_set = strpos(ini_get('sendmail_path'), ' -f');
      if (!$return_path_set) {
        $message['Return-Path'] = $message['headers']['Return-Path'];
        unset($message['headers']['Return-Path']);
      }
    }

    /*
    $mail_subject = mime_header_encode($message['subject']);
    $line_endings = variable_get('mail_line_endings', MAIL_LINE_ENDINGS);
    $mail_body = preg_replace('@\r?\n@', $line_endings, $message['body']);
    $mail_headers = mimemail_rfc_headers($message['headers']);
    */
    $mimeheaders = array();
    foreach ($message['headers'] as $name => $value) {
      $mimeheaders[] = $name . ': ' . mime_header_encode($value);
    }
    $line_endings = variable_get('mail_line_endings', MAIL_LINE_ENDINGS);

    // Prepare mail commands.
    $mail_subject = mime_header_encode($message['subject']);

    // Note: e-mail uses CRLF for line-endings. PHP's API requires LF
    // on Unix and CRLF on Windows. Drupal automatically guesses the
    // line-ending format appropriate for your system. If you need to
    // override this, adjust $conf['mail_line_endings'] in settings.php.
    $mail_body = preg_replace('@\\r?\\n@', $line_endings, $message['body']);

    // For headers, PHP's API suggests that we use CRLF normally,
    // but some MTAs incorrectly replace LF with CRLF. See #234403.
    $mail_headers = join("\n", $mimeheaders);
    if (isset($message['Return-Path']) && !ini_get('safe_mode')) {
      $mail_result = mail($message['to'], $mail_subject, $mail_body, $mail_headers, '-f ' . $message['Return-Path']);
    }
    else {

      // The optional $additional_parameters argument to mail() is not allowed
      // if safe_mode is enabled. Passing any value throws a PHP warning and
      // makes mail() return FALSE.
      $mail_result = mail($message['to'], $mail_subject, $mail_body, $mail_headers);
    }
    return $mail_result;
  }

  /**
   * Generate a multipart message body with a text alternative for some html text
   * @param $body An HTML message body
   * @param $subject The message subject
   *
   * @return
   *     an array containing the elements 'header' and 'body'.
   *     'body' is the mime encoded multipart body of a mail.
   *     'headers' is an array that includes some headers for the mail to be sent.
   *
   * The first mime part is a multipart/alternative containing mime-encoded
   * sub-parts for HTML and plaintext.  Each subsequent part is the required
   * image/attachment
   */
  function mimemail_html_body($body, $subject, $text = NULL, $attachments = array()) {
    if (empty($text)) {

      // todo: remove this preg_replace once filter_xss() is properly handling
      // direct descendant css selectors '>' in inline CSS. For now this cleans
      // up our plain text part. See mimemail #364198, drupal #370903
      $text = preg_replace('|<style.*?</style>|mis', '', $body);
      $text = drupal_html_to_text($text);
    }
    $content_type = 'multipart/related; type="multipart/alternative"';
    $text_part = array(
      'Content-Type' => 'text/plain; charset=utf-8',
      'content' => $text,
    );

    /**
         * @todo When replacing links we break the ones that are yet to be replaced (tokens)
        //expand all local links
        $pattern = '/(<a[^>]+href=")([^"]*)/mi';
        $body = preg_replace_callback($pattern, '_messaging_htmlmail_expand_links', $body);
        **/
    $mime_parts = $this
      ->mimemail_extract_files($body);
    $content = array(
      $text_part,
      array_shift($mime_parts),
    );
    $content = $this
      ->mimemail_multipart_body($content, 'multipart/alternative', TRUE);
    $parts = array(
      array(
        'Content-Type' => $content['headers']['Content-Type'],
        'content' => $content['body'],
      ),
    );
    if ($mime_parts) {
      $parts = array_merge($parts, $mime_parts);
    }
    foreach ($attachments as $a) {
      $a = (object) $a;

      // Check the list parameter if its set or ignore it (Upload module support).
      if (!isset($a->list) || $a->list) {
        _messaging_htmlmail_file($a->filepath, $a->filename, $a->filemime, 'attachment');
        $parts = array_merge($parts, _messaging_htmlmail_file());
      }
    }
    return $this
      ->mimemail_multipart_body($parts, $content_type);
  }

  /**
   *
   * @param $parts
   *        an array of parts to be included
   *        each part is itself an array:
   *        array(
   *          'name' => $name the name of the attachement
   *          'content' => $content textual content
   *          'file' => $file a file
   *          'Content-Type' => Content type of either file or content.
   *                            Mandatory for content, optional for file.
   *                            If not present, it will be derived from
   *                            file the file if mime_content_type is available.
   *                            If not, application/octet-stream is used.
   *          'Content-Disposition' => optional, inline is assumed
   *          'Content-Transfer-Encoding' => optional,
   *                                         base64 is assumed for files
   *                                         8bit for other content.
   *          'Content-ID' => optional, for in-mail references to attachements.
   *        )
   *        name is mandatory, one of content and file is required,
   *        they are mutually exclusive.
   *
   * @param $content_type
   *        Content-Type for the combined message, optional, default: multipart/mixed
   *
   * @return
   *     an array containing the elements 'header' and 'body'.
   *     'body' is the mime encoded multipart body of a mail.
   *     'headers' is an array that includes some headers for the mail to be sent.
   */
  function mimemail_multipart_body($parts, $content_type = 'multipart/mixed; charset=utf-8', $sub_part = FALSE) {
    $boundary = md5(uniqid(time()));
    $body = '';
    $headers = array(
      'Content-Type' => "{$content_type}; boundary=\"{$boundary}\"",
    );
    if (!$sub_part) {
      $headers['MIME-Version'] = '1.0';
      $body = "This is a multi-part message in MIME format.\n";
    }
    foreach ($parts as $part) {
      $part_headers = array();
      if (isset($part['Content-ID'])) {
        $part_headers['Content-ID'] = '<' . $part['Content-ID'] . '>';
      }
      if (isset($part['Content-Type'])) {
        $part_headers['Content-Type'] = $part['Content-Type'];
      }
      if (isset($part['Content-Disposition'])) {
        $part_headers['Content-Disposition'] = $part['Content-Disposition'];
      }
      else {
        $part_headers['Content-Disposition'] = 'inline';
      }
      if (isset($part['Content-Transfer-Encoding'])) {
        $part_headers['Content-Transfer-Encoding'] = $part['Content-Transfer-Encoding'];
      }

      // mail content provided as a string
      if (isset($part['content']) && $part['content']) {
        if (!isset($part['Content-Transfer-Encoding'])) {
          $part_headers['Content-Transfer-Encoding'] = '8bit';
        }
        $part_body = $part['content'];
        if (isset($part['name'])) {
          $part_headers['Content-Type'] .= '; name="' . $part['name'] . '"';
          $part_headers['Content-Disposition'] .= '; filename="' . $part['name'] . '"';
        }

        // mail content references in a filename
      }
      else {
        if (!isset($part['Content-Transfer-Encoding'])) {
          $part_headers['Content-Transfer-Encoding'] = 'base64';
        }
        if (!isset($part['Content-Type'])) {
          $part['Content-Type'] = file_get_mimetype($part['file']);
        }
        if (isset($part['name'])) {
          $part_headers['Content-Type'] .= '; name="' . $part['name'] . '"';
          $part_headers['Content-Disposition'] .= '; filename="' . $part['name'] . '"';
        }
        if (isset($part['file'])) {
          $part_body = chunk_split(base64_encode(file_get_contents($part['file'])));
        }
      }
      $body .= "\n--{$boundary}\n";
      $body .= $this
        ->mimemail_rfc_headers($part_headers) . "\n\n";
      $body .= $part_body;
    }
    $body .= "\n--{$boundary}--\n";
    return array(
      'headers' => $headers,
      'body' => $body,
    );
  }

  /**
   * Extracts links to local images from html documents.
   *
   * @param $html html text
   * @param $name document name
   *
   * @return an array of arrays
   *            array(array(
   *                     'name' => document name
   *                     'content' => html text, local image urls replaced by Content-IDs,
   *                     'Content-Type' => 'text/html; charset=utf-8')
   *                  array(
   *                     'name' => file name,
   *                     'file' => reference to local file,
   *                     'Content-ID' => generated Content-ID,
   *                     'Content-Type' => derived using mime_content_type
   *                                       if available, educated guess otherwise
   *                     )
   *                  )
   */
  function mimemail_extract_files($html) {
    $pattern = '/(<link[^>]+href=[\'"]?|<object[^>]+codebase=[\'"]?|@import |[\\s]src=[\'"]?)([^\'>"]+)([\'"]?)/mis';
    $html = preg_replace_callback($pattern, '_messaging_htmlmail_replace_files', $html);
    $document = array(
      array(
        'Content-Type' => "text/html; charset=utf-8",
        'Content-Transfer-Encoding' => '8bit',
        'content' => $html,
      ),
    );
    $files = _messaging_htmlmail_file();
    return array_merge($document, $files);
  }

  /**
   * Attempts to RFC822-compliant headers for the mail message or its MIME parts
   * TODO could use some enhancement and stress testing
   *
   * @param $headers An array of headers
   * @return header string
   */
  function mimemail_rfc_headers($headers) {
    $header = '';
    $crlf = variable_get('mimemail_crlf', "\n");
    foreach ($headers as $key => $value) {
      $key = trim($key);

      // collapse spaces and get rid of newline characters
      $value = preg_replace('/(\\s+|\\n|\\r|^\\s|\\s$)/', ' ', $value);

      //fold headers if they're too long
      if (strlen($value) > 60) {

        //if there's a semicolon, use that to separate
        if (count($array = preg_split('/;\\s*/', $value)) > 1) {
          $value = trim(join(";{$crlf}    ", $array));
        }
        else {
          $value = wordwrap($value, 50, "{$crlf}    ", FALSE);
        }
      }
      $header .= "{$key}: {$value}{$crlf}";
    }
    return trim($header);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DefaultMailSystem::_isShellSafe protected static function Disallows potentially unsafe shell characters.
Messaging_HTML_MailSystem::format public function Concatenate and wrap the e-mail body for plain-text mails. Overrides DefaultMailSystem::format
Messaging_HTML_MailSystem::mail public function Send an e-mail message, using Drupal variables and default settings. Overrides DefaultMailSystem::mail
Messaging_HTML_MailSystem::mimemail_extract_files function Extracts links to local images from html documents.
Messaging_HTML_MailSystem::mimemail_html_body function Generate a multipart message body with a text alternative for some html text
Messaging_HTML_MailSystem::mimemail_multipart_body function
Messaging_HTML_MailSystem::mimemail_rfc_headers function Attempts to RFC822-compliant headers for the mail message or its MIME parts TODO could use some enhancement and stress testing