You are here

emogrifier.module in Emogrifier 8

Same filename and directory in other branches
  1. 6 emogrifier.module
  2. 7.2 emogrifier.module
  3. 7 emogrifier.module

File

emogrifier.module
View source
<?php

/**
 * @file
 * This module uses the emogrifier class library as an input filter to convert
 * stylesheet rules to inline style attributes.  This ensures proper display
 * on email and mobile device readers that lack stylesheet support.
 *
 * @see http://www.pelagodesign.com/sidecar/emogrifier/
 */
if (!function_exists('url_to_path')) {
  require_once dirname(__FILE__) . '/url_to_path.inc';
}

/**
 * Implements hook_help().
 */
function emogrifier_help($path = 'admin/help#emogrifier', $arg) {
  switch ($path) {
    case 'admin/help#emogrifier':
      return '<p>' . t('The <a href="!module">%emogrifier</a> module uses the <a href="!class">%emogrifier</a> class library to convert stylesheet rules to inline style attributes. This ensures proper display on email and mobile device readers that lack stylesheet support.', array(
        '!module' => url('http://drupal.org/project/emogrifier'),
        '%emogrifier' => 'emogrifier',
        '!class' => url('http://www.pelagodesign.com/sidecar/emogrifier/'),
      )) . '</p>';
      break;
  }
}

/**
 * Implements hook_filter_info().
 */
function emogrifier_filter_info() {
  return array(
    'filter_emogrifier' => array(
      'title' => t('Emogrifier'),
      'description' => t('Converts stylesheet rules to inline style attributes.'),
      'process callback' => '_emogrifier_process',
      'settings callback' => '_emogrifier_filter_settings',
      'tips callback' => '_emogrifier_filter_tips',
    ),
  );
}

/**
 * Implements hook_filter_FILTER_process().
 * @see emogrifier_filter_info()
 */
function _emogrifier_process($text, $filter, $format, $langcode, $cache, $cache_id) {
  if (empty($text)) {
    return '';
  }

  // Avoid PHP fatal errors when the 'dom' extension is not loaded.
  if (!extension_loaded('dom')) {
    watchdog('emogrifier', 'The PHP <a href="!dom">%dom</a> extension required by <a href="!emogrifier">%emogrifier</a> is not loaded.', array(
      '!dom' => url('http://php.net/dom'),
      '%dom' => 'dom',
      '!emogrifier' => url('http://drupal.org/project/emogrifier'),
      '%emogrifier' => 'Emogrifier',
    ), LOG_WARNING);
    return $text;
  }
  if (!_emogrifier_available()) {
    watchdog('emogrifier', 'The <a href="!library">%emogrifier</a> class library required by the <a href="!module">%emogrifier</a> module could not be loaded.', array(
      '!library' => url('http://www.pelagodesign.com/sidecar/emogrifier/'),
      '%emogrifier' => 'emogrifier',
      '!module' => url('http://drupal.org/project/emogrifier'),
    ), LOG_WARNING);
    return $text;
  }
  $styles = array();
  $urls = array();

  // Match linked stylesheets.
  if (preg_match_all('#<link[^>]*>\\s*#si', $text, $links)) {
    foreach ($links[0] as $link) {
      if (preg_match('#\\s+type\\s*=\\s*(["\']?)text/css\\1#si', $link) && preg_match('#\\s+rel\\s*=\\s*(["\']?)stylesheet\\1#si', $link) && preg_match('#\\s+media\\s*=\\s*(["\']?)all\\1#si', $link) && preg_match('#\\s+href\\s*=\\s*(["\']?)([^?]*?)(\\?.*?)?\\1#si', $link, $match)) {
        $urls = array_merge($urls, array_slice($match, 2, 1));
      }
      $text = str_replace($link, '', $text);
    }
  }

  // Match style blocks, possibly with @imports.
  if (preg_match_all('#(<style[^>]*>)\\s*(<!--)?\\s*(.*?)\\s*(-->)?\\s*</style>\\s*#si', $text, $blocks)) {
    foreach ($blocks[1] as $block => $tag) {
      if (preg_match('#\\s+type\\s*=\\s*(["\']?)text/css\\1#si', $tag) && preg_match('#\\s+media\\s*=\\s*(["\']?)all\\1#si', $tag)) {
        if (preg_match_all('#@import\\s+url\\s*\\(\\s*(["\']?)([^?]*)(\\?.*?)?\\1\\s*\\)\\s*;#si', $blocks[3][$block], $imports)) {
          $urls = array_merge($urls, $imports[2]);
          $rules = trim(str_replace($imports[0], '', $blocks[3][$block]));
          if (!empty($rules)) {
            $styles[] = $rules;
          }
        }
      }
    }
    $text = str_replace($blocks[0], '', $text);
  }
  foreach ($urls as $url) {
    if (($path = url_to_realpath(check_url($url))) && file_exists($path) && ($style = trim(file_get_contents($path)))) {
      $styles[] = $style;
    }
  }

  // Strip comments and unecessary whitespace from the collected style rules.
  $styles = preg_replace(array(
    '/[[:space:]]+/s',
    '#/\\*.*?\\*/#',
    '/ +/',
  ), ' ', $styles);

  // Remove quote marks around urls.
  $styles = preg_replace('/url[[:space:]]*\\([[:space:]]*(["\'])(.*?)\\1[[:space:]]*\\)/mis', 'url(\\2)', $styles);
  $text = preg_replace('/url[[:space:]]*\\([[:space:]]*(["\'])(.*?)\\1[[:space:]]*\\)/mis', 'url(\\2)', $text);

  // Emogrify can't handle several CSS rules on one line,
  // so insert a newline character after each closing bracket.
  $styles = preg_replace('/}\\s*/', "}\n", implode("\n", $styles));

  // Strip unwanted container tags.
  $text = preg_replace('#<(applet|audio|canvas|embed|frameset|head|iframe|object|ruby|script|style|title|video)[^[:alnum:]].*?</\\1\\s*>\\s*#si', '', '<head>' . $text);

  // Replace or remove other tags.
  $text = preg_replace_callback('#(<(/?))([!\\[[:alnum:]\\]]{2,12})([^>]*>)#s', '_emogrifier_replace_tag', $text);

  // Strip unwanted container tags replaced by comments.
  $text = preg_replace('#<!--.*?-->\\s*#si', '', $text);

  // Load the html text and style rules.
  $emogrifier = new Emogrifier('<html><body>' . $text . '</body></html>', $styles);

  // Apply the rules to create inline style attributes.
  $text = @$emogrifier
    ->emogrify();

  // Extract body portion.
  $text = preg_replace('#.*<body>(.*)</body>.*#Usi', '\\1', $text);

  // Strip unwanted attributes.
  $text = preg_replace(array(
    '#(<[^>]*?)\\s*class\\s*=\\s*(["\']).*?\\2#si',
    '#(<[^>]*?)\\s*id\\s*=\\s*(["\']).*?\\2#si',
  ), '\\1', $text);

  // @todo Strip any whitespace outside of <pre> or <code> tags?
  return $text;
}
function _emogrifier_replace_tag(array $matches) {
  static $allowed;
  if (!isset($allowed)) {
    $allowed =& emogrifier_allowed_tags();
  }
  return isset($allowed[$name = strtolower($matches[3])]) ? is_array($tag = $allowed[$name]) ? $tag[(bool) $matches[2]] : $matches[1] . $tag . $matches[4] : '';

  // All unlisted tags are removed, but their contents remain.
}
function &emogrifier_allowed_tags() {
  $tags = drupal_static(__FUNCTION__);
  if (!isset($tags)) {
    $tags = variable_get('emogrifier_allowed_tags', array_combine($a = explode(',', 'a,abbr,address,area,b,bdo,blockquote,br,button,caption,cite,' . 'code,col,colgroup,dd,del,dfn,div,dl,dt,em,fieldset,font,form,h1,h2,' . 'h3,h4,h5,h6,hr,i,img,input,ins,kbd,label,legend,li,map,ol,optgroup,' . 'option,p,pre,q,s,samp,select,span,strike,strong,sub,sup,table,' . 'tbody,td,textarea,tfoot,th,thead,tr,tt,u,ul,var,'), $a) + array_fill_keys(explode(',', 'acronym,meter,output,progress,time'), 'span') + array_fill_keys(explode(',', 'article,aside,body,details,figcaption,figure,footer,header,' . 'hgroup,nav,section,summary'), 'div') + array(
      // Other tag replacements.
      'big' => array(
        '<span style="font-size: 120%">',
        '</span>',
      ),
      'center' => array(
        '<div style="text-align: center">',
        '</div>',
      ),
      'command' => 'button',
      'datalist' => 'select',
      'dir' => 'ul',
      'mark' => array(
        '<span style="background-color: yellow; color: black">',
        '</span>',
      ),
      'menu' => 'ul',
      's' => array(
        '<span style="text-decoration: line-through">',
        '</span>',
      ),
      'small' => array(
        '<span style="font-size: 80%">',
        '</span>',
      ),
      'strike' => array(
        '<span style="text-decoration: line-through">',
        '</span>',
      ),
      'wbr' => array(
        '&shy;',
        '',
      ),
      'xmp' => 'pre',
    ));
  }
  return $tags;
}

/**
 * Returns TRUE if the Emogrifier class is available.
 */
function _emogrifier_available() {

  // Maybe something loaded the class without telling libraries API.
  if (class_exists('Emogrifier')) {
    return TRUE;
  }
  @(include_once libraries_get_path('emogrifier') . '/emogrifier.php');
  return class_exists('Emogrifier');
}

/**
 * Implements hook_filter_FILTER_settings().
 * @see emogrifier_filter_info()
 */
function _emogrifier_filter_settings($form, &$form_state, $filter, $format, $defaults, $filters) {
  return array();
}

/**
 * Implements hook_filter_FILTER_tips().
 * @see emogrifier_filter_info()
 */
function _emogrifier_filter_tips($filter, $format, $long) {
  return t('Stylesheet rules will be converted to inline style attributes.');
}

Functions

Namesort descending Description
emogrifier_allowed_tags
emogrifier_filter_info Implements hook_filter_info().
emogrifier_help Implements hook_help().
_emogrifier_available Returns TRUE if the Emogrifier class is available.
_emogrifier_filter_settings Implements hook_filter_FILTER_settings().
_emogrifier_filter_tips Implements hook_filter_FILTER_tips().
_emogrifier_process Implements hook_filter_FILTER_process().
_emogrifier_replace_tag