You are here

public function mimemail_compress::compress in Mime Mail 7

Same name and namespace in other branches
  1. 6 modules/mimemail_compress/mimemail_compress.inc \mimemail_compress::compress()

File

modules/mimemail_compress/mimemail_compress.inc, line 66
Converts CSS styles into inline style attributes.

Class

mimemail_compress
Compresses HTML and CSS into combined message.

Code

public function compress() {
  if (!class_exists('DOMDocument', FALSE)) {
    return $this->html;
  }
  $body = $this->html;

  // Process the CSS here, turning the CSS style blocks into inline CSS.
  if (count($this->unprocessable_tags)) {
    $unprocessable_tags = implode('|', $this->unprocessable_tags);
    $body = preg_replace("/<({$unprocessable_tags})[^>]*>/i", '', $body);
  }
  $err = error_reporting(0);
  $doc = new DOMDocument();

  // Try to set character encoding.
  if (function_exists('mb_convert_encoding')) {
    $body = mb_convert_encoding($body, 'HTML-ENTITIES', "UTF-8");
    $doc->encoding = "UTF-8";
  }
  $doc->strictErrorChecking = FALSE;
  $doc->formatOutput = TRUE;
  $doc
    ->loadHTML($body);
  $doc
    ->normalizeDocument();
  $xpath = new DOMXPath($doc);

  // Get rid of comments.
  $css = preg_replace('/\\/\\*.*\\*\\//sU', '', $this->css);

  // Process the CSS file for selectors and definitions.
  preg_match_all('/^\\s*([^{]+){([^}]+)}/mis', $css, $matches);
  $all_selectors = array();
  foreach ($matches[1] as $key => $selector_string) {

    // If there is a blank definition, skip.
    if (!strlen(trim($matches[2][$key]))) {
      continue;
    }

    // Else split by commas and duplicate attributes so we can sort by
    // selector precedence.
    $selectors = explode(',', $selector_string);
    foreach ($selectors as $selector) {

      // Don't process pseudo-classes.
      if (strpos($selector, ':') !== FALSE) {
        continue;
      }
      $all_selectors[] = array(
        'selector' => $selector,
        'attributes' => $matches[2][$key],
        // Keep track of where it appears in the file,
        // since order is important.
        'index' => $key,
      );
    }
  }

  // Now sort the selectors by precedence.
  usort($all_selectors, array(
    'self',
    'sort_selector_precedence',
  ));

  // Before we begin processing the CSS file, parse the document for inline
  // styles and append the normalized properties (i.e., 'display: none'
  // instead of 'DISPLAY: none') as selectors with full paths (highest
  // precedence), so they override any file-based selectors.
  $nodes = @$xpath
    ->query('//*[@style]');
  if ($nodes->length > 0) {
    foreach ($nodes as $node) {
      $style = preg_replace_callback('/[A-z\\-]+(?=\\:)/S', array(
        $this,
        'string_to_lower',
      ), $node
        ->getAttribute('style'));
      $all_selectors[] = array(
        'selector' => $this
          ->calculateXPath($node),
        'attributes' => $style,
      );
    }
  }
  foreach ($all_selectors as $value) {

    // Query the body for the xpath selector.
    $nodes = $xpath
      ->query($this
      ->css_to_xpath(trim($value['selector'])));
    foreach ($nodes as $node) {

      // If it has a style attribute, get it, process it, and append
      // (overwrite) new stuff.
      if ($node
        ->hasAttribute('style')) {

        // Break it up into an associative array.
        $old_style = $this
          ->css_style_to_array($node
          ->getAttribute('style'));
        $new_style = $this
          ->css_style_to_array($value['attributes']);

        // New styles overwrite the old styles (not technically accurate,
        // but close enough).
        $compressed = array_merge($old_style, $new_style);
        $style = '';
        foreach ($compressed as $k => $v) {
          $style .= drupal_strtolower($k) . ':' . $v . ';';
        }
      }
      else {

        // Otherwise create a new style.
        $style = trim($value['attributes']);
      }
      $node
        ->setAttribute('style', $style);

      // Convert float to align for images.
      $float = preg_match('/float:(left|right)/', $style, $matches);
      if ($node->nodeName == 'img' && $float) {
        $node
          ->setAttribute('align', $matches[1]);
        $node
          ->setAttribute('vspace', 5);
        $node
          ->setAttribute('hspace', 5);
      }
    }
  }

  // This removes styles from your email that contain display:none.
  // You could comment these out if you want.
  $nodes = $xpath
    ->query('//*[contains(translate(@style," ",""), "display:none")]');
  foreach ($nodes as $node) {
    $node->parentNode
      ->removeChild($node);
  }
  if (variable_get('mimemail_preserve_class', 0) == FALSE) {
    $nodes = $xpath
      ->query('//*[@class]');
    foreach ($nodes as $node) {
      $node
        ->removeAttribute('class');
    }
  }
  error_reporting($err);
  return $doc
    ->saveHTML();
}