Make CSS inline in HTML mails
[bdrem.git] / src / bdrem / Renderer / Mail.php
index d7aef3e..0efa3af 100644 (file)
@@ -29,6 +29,18 @@ require_once 'Mail/mime.php';
 class Renderer_Mail extends Renderer
 {
     /**
+     * Add HTML part to email
+     * @var bool
+     */
+    public $html = true;
+
+    /**
+     * CSS "inline" in tags, or "separate" in a style block
+     * @var string
+     */
+    public $css = 'inline';
+
+    /**
      * Render the events - send out mails.
      *
      * Uses the config's "mail_to" array as recipients.
@@ -52,8 +64,8 @@ class Renderer_Mail extends Renderer
             $subject .= ': ' . implode(', ', $todays);
         }
 
-        $rc = new Renderer_Console();
-        $rh = new Renderer_Html();
+        $rc  = new Renderer_Console();
+        $rht = new Renderer_HtmlTable();
 
         $hdrs = array(
             'From'    => $this->config->get('mail_from', 'birthday@example.org'),
@@ -69,7 +81,20 @@ class Renderer_Mail extends Renderer
         );
 
         $mime->setTXTBody($rc->render($arEvents));
-        $mime->setHTMLBody($rh->render($arEvents));
+        if ($this->html) {
+            if ($this->css == 'inline') {
+                $html = $this->inlineCss(
+                    $rht->render($arEvents),
+                    Renderer_Html::getCss()
+                );
+            } else {
+                $html = '<style type="text/css">'
+                    . Renderer_Html::getCss()
+                    . '</style>'
+                    . $rht->render($arEvents);
+            }
+            $mime->setHTMLBody($this->minifyHtml($html));
+        }
 
         $body = $mime->get();
         $hdrs = $mime->headers($hdrs);
@@ -107,5 +132,113 @@ class Renderer_Mail extends Renderer
 
         return mb_substr($str, 0, $len - 1) . '…';
     }
+
+    /**
+     * Takes the HTML and CSS code and inlines CSS into HTML.
+     *
+     * This is important for some e-mail clients which do
+     * not interpret <style> tags but only support inline styles.
+     *
+     * Works nicely with bdrem's CSS. If you need more CSS selector
+     * support, have a look at https://github.com/jjriv/emogrifier
+     *
+     * @param string $html HTML code
+     * @param string $css  CSS code
+     *
+     * @return string HTML with inlined CSS
+     */
+    protected function inlineCss($html, $css)
+    {
+        preg_match_all(
+            '#([^{]+) {([^}]+)}#m',
+            $css,
+            $parts
+        );
+        $rules = array();
+        foreach ($parts[1] as $key => $rule) {
+            $mrules = explode(',', $rule);
+            foreach ($mrules as $rule) {
+                $rule  = trim($rule);
+                $style = trim($parts[2][$key]);
+                $rules[$rule] = preg_replace(
+                    '#([:;]) +#', '\1',
+                    str_replace(
+                        ["\r", "\n", '    '],
+                        ['', '', ' '],
+                        $style
+                    )
+                );
+            }
+        }
+        $sx = simplexml_load_string($html);
+        foreach ($rules as $rule => $style) {
+            $mode = null;
+            $parts = explode(' ', $rule);
+            $xp = '';
+            foreach ($parts as $part) {
+                $part = trim($part);
+                if (strpos($part, ':') !== false) {
+                    //.foo:before
+                    list($part, $mode) = explode(':', $part);
+                    if ($mode == 'hover') {
+                        continue 2;
+                    }
+                }
+                if (strpos($part, '.') === false) {
+                    //tag only
+                    if ($part == '') {
+                        $xp = '//*';
+                    } else {
+                        $xp .= '//' . $part;
+                    }
+                } else {
+                    //tag.class
+                    list($tag, $class) = explode('.', $part);
+                    if ($tag == '') {
+                        $tag = '*';
+                    }
+                    $xp .= '//' . $tag
+                        . '[contains('
+                        . 'concat(" ", normalize-space(@class), " "), '
+                        . '" ' . $class . ' "'
+                        . ')]';
+                }
+            }
+            $res = $sx->xpath($xp);
+            //var_dump($res);die();
+            //var_dump($xp, $style);
+            foreach ($res as $xelem) {
+                if ($mode === null) {
+                    $xelem['style'] .= $style;
+                } else if ($mode == 'before') {
+                    $xelem[0] = preg_replace(
+                        '#content:\s*"(.+)"#', '\1', $style
+                    );
+                }
+            }
+        }
+
+        $html = $sx->asXML();
+        //strip xml header
+        $lines = explode("\n", $html);
+        unset($lines[0]);
+        $html = implode("\n", $lines);
+
+        //echo $html . "\n";die();
+        return $html;
+    }
+
+    /**
+     * Remove whitespace between tags
+     *
+     * @param string $html HTML code
+     *
+     * @return string Smaller HTML code
+     */
+    protected function minifyHtml($html)
+    {
+        $html = trim(preg_replace("#[\n\r ]+<#", '<', $html));
+        return $html;
+    }
 }
 ?>
\ No newline at end of file