Update phing build script to use composer installation only
[bdrem.git] / src / bdrem / Renderer / Mail.php
1 <?php
2 /**
3  * Part of bdrem
4  *
5  * PHP version 5
6  *
7  * @category  Tools
8  * @package   Bdrem
9  * @author    Christian Weiske <cweiske@cweiske.de>
10  * @copyright 2014 Christian Weiske
11  * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
12  * @link      http://cweiske.de/bdrem.htm
13  */
14 namespace bdrem;
15
16 require_once 'Mail/mime.php';
17
18 /**
19  * Send out mails
20  *
21  * @category  Tools
22  * @package   Bdrem
23  * @author    Christian Weiske <cweiske@cweiske.de>
24  * @copyright 2014 Christian Weiske
25  * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
26  * @version   Release: @package_version@
27  * @link      http://cweiske.de/bdrem.htm
28  */
29 class Renderer_Mail extends Renderer
30 {
31     /**
32      * Add HTML part to email
33      * @var bool
34      */
35     public $html = true;
36
37     /**
38      * CSS "inline" in tags, or "separate" in a style block
39      * @var string
40      */
41     public $css = 'inline';
42
43     /**
44      * Render the events - send out mails.
45      *
46      * Uses the config's "mail_to" array as recipients.
47      * Sends out a single mail for each recipient.
48      * Config "mail_from" can also be used.
49      *
50      * @param array $arEvents Array of events to display
51      *
52      * @return void
53      */
54     public function render($arEvents)
55     {
56         $todays = array();
57         foreach ($arEvents as $event) {
58             if ($event->days == 0) {
59                 $todays[] = $this->shorten($event->title, 15);
60             }
61         }
62         $subject = 'Birthday reminder';
63         if (count($todays)) {
64             $subject .= ': ' . implode(', ', $todays);
65         }
66
67         $rc  = new Renderer_Console();
68         $rht = new Renderer_HtmlTable();
69
70         $hdrs = array(
71             'From'    => $this->config->get('mail_from', 'birthday@example.org'),
72             'Date'    => date('r'),
73             'Auto-Submitted' => 'auto-generated'
74         );
75         $mime = new \Mail_mime(
76             array(
77                 'eol' => "\n",
78                 'head_charset' => 'utf-8',
79                 'text_charset' => 'utf-8',
80                 'html_charset' => 'utf-8',
81             )
82         );
83
84         $mime->setTXTBody($rc->render($arEvents));
85         if ($this->html) {
86             if ($this->css == 'inline') {
87                 $html = $this->inlineCss(
88                     $rht->render($arEvents),
89                     Renderer_Html::getCss()
90                 );
91             } else {
92                 $html = '<style type="text/css">'
93                     . Renderer_Html::getCss()
94                     . '</style>'
95                     . $rht->render($arEvents);
96             }
97             $mime->setHTMLBody($this->minifyHtml($html));
98         }
99
100         $body = $mime->get();
101         $hdrs = $mime->headers($hdrs);
102         $textHeaders = '';
103         foreach ($hdrs as $k => $v) {
104             $textHeaders .= $k . ': ' . $v  . "\n";
105         }
106
107         if (!$this->config->get('debug', false)) {
108             foreach ((array) $this->config->get('mail_to') as $recipient) {
109                 mail($recipient, $subject, $body, $textHeaders);
110             }
111         } else {
112             echo "Subject: " . $subject . "\n";
113             echo $textHeaders;
114             echo "\n";
115             echo $body;
116         }
117     }
118
119     /**
120      * Shorten the given string to the specified length.
121      * Adds ... when the string was too long
122      *
123      * @param string  $str String to shorten
124      * @param integer $len Maximum length of the string
125      *
126      * @return string Shortened string
127      */
128     protected function shorten($str, $len)
129     {
130         if (mb_strlen($str) <= $len) {
131             return $str;
132         }
133
134         return mb_substr($str, 0, $len - 1) . '…';
135     }
136
137     /**
138      * Takes the HTML and CSS code and inlines CSS into HTML.
139      *
140      * This is important for some e-mail clients which do
141      * not interpret <style> tags but only support inline styles.
142      *
143      * Works nicely with bdrem's CSS. If you need more CSS selector
144      * support, have a look at https://github.com/jjriv/emogrifier
145      *
146      * @param string $html HTML code
147      * @param string $css  CSS code
148      *
149      * @return string HTML with inlined CSS
150      */
151     protected function inlineCss($html, $css)
152     {
153         preg_match_all(
154             '#([^{]+) {([^}]+)}#m',
155             $css,
156             $parts
157         );
158         $rules = array();
159         foreach ($parts[1] as $key => $rule) {
160             $mrules = explode(',', $rule);
161             foreach ($mrules as $rule) {
162                 $rule  = trim($rule);
163                 $style = trim($parts[2][$key]);
164                 $rules[$rule] = preg_replace(
165                     '#([:;]) +#', '\1',
166                     str_replace(
167                         ["\r", "\n", '    '],
168                         ['', '', ' '],
169                         $style
170                     )
171                 );
172             }
173         }
174         $sx = simplexml_load_string($html);
175         foreach ($rules as $rule => $style) {
176             $mode = null;
177             $parts = explode(' ', $rule);
178             $xp = '';
179             foreach ($parts as $part) {
180                 $part = trim($part);
181                 if (strpos($part, ':') !== false) {
182                     //.foo:before
183                     list($part, $mode) = explode(':', $part);
184                     if ($mode == 'hover') {
185                         continue 2;
186                     }
187                 }
188                 if (strpos($part, '.') === false) {
189                     //tag only
190                     if ($part == '') {
191                         $xp = '//*';
192                     } else {
193                         $xp .= '//' . $part;
194                     }
195                 } else {
196                     //tag.class
197                     list($tag, $class) = explode('.', $part);
198                     if ($tag == '') {
199                         $tag = '*';
200                     }
201                     $xp .= '//' . $tag
202                         . '[contains('
203                         . 'concat(" ", normalize-space(@class), " "), '
204                         . '" ' . $class . ' "'
205                         . ')]';
206                 }
207             }
208             $res = $sx->xpath($xp);
209             //var_dump($res);die();
210             //var_dump($xp, $style);
211             foreach ($res as $xelem) {
212                 if ($mode === null) {
213                     $xelem['style'] .= $style;
214                 } else if ($mode == 'before') {
215                     $xelem[0] = preg_replace(
216                         '#content:\s*"(.+)"#', '\1', $style
217                     );
218                 }
219             }
220         }
221
222         $html = $sx->asXML();
223         //strip xml header
224         $lines = explode("\n", $html);
225         unset($lines[0]);
226         $html = implode("\n", $lines);
227
228         //echo $html . "\n";die();
229         return $html;
230     }
231
232     /**
233      * Remove whitespace between tags
234      *
235      * @param string $html HTML code
236      *
237      * @return string Smaller HTML code
238      */
239     protected function minifyHtml($html)
240     {
241         $html = trim(preg_replace("#[\n\r ]+<#", '<', $html));
242         return $html;
243     }
244 }
245 ?>