1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Mail;
9:
10: use Nette,
11: Nette\Utils\Strings;
12:
13:
14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24:
25: class Message extends MimePart
26: {
27:
28: const HIGH = 1,
29: NORMAL = 3,
30: LOW = 5;
31:
32:
33: public static = array(
34: 'MIME-Version' => '1.0',
35: 'X-Mailer' => 'Nette Framework',
36: );
37:
38:
39: private $attachments = array();
40:
41:
42: private $inlines = array();
43:
44:
45: private $html;
46:
47:
48: public function __construct()
49: {
50: foreach (static::$defaultHeaders as $name => $value) {
51: $this->setHeader($name, $value);
52: }
53: $this->setHeader('Date', date('r'));
54: }
55:
56:
57: 58: 59: 60: 61: 62:
63: public function setFrom($email, $name = NULL)
64: {
65: $this->setHeader('From', $this->formatEmail($email, $name));
66: return $this;
67: }
68:
69:
70: 71: 72: 73:
74: public function getFrom()
75: {
76: return $this->getHeader('From');
77: }
78:
79:
80: 81: 82: 83: 84: 85:
86: public function addReplyTo($email, $name = NULL)
87: {
88: $this->setHeader('Reply-To', $this->formatEmail($email, $name), TRUE);
89: return $this;
90: }
91:
92:
93: 94: 95: 96: 97:
98: public function setSubject($subject)
99: {
100: $this->setHeader('Subject', $subject);
101: return $this;
102: }
103:
104:
105: 106: 107: 108:
109: public function getSubject()
110: {
111: return $this->getHeader('Subject');
112: }
113:
114:
115: 116: 117: 118: 119: 120:
121: public function addTo($email, $name = NULL)
122: {
123: $this->setHeader('To', $this->formatEmail($email, $name), TRUE);
124: return $this;
125: }
126:
127:
128: 129: 130: 131: 132: 133:
134: public function addCc($email, $name = NULL)
135: {
136: $this->setHeader('Cc', $this->formatEmail($email, $name), TRUE);
137: return $this;
138: }
139:
140:
141: 142: 143: 144: 145: 146:
147: public function addBcc($email, $name = NULL)
148: {
149: $this->setHeader('Bcc', $this->formatEmail($email, $name), TRUE);
150: return $this;
151: }
152:
153:
154: 155: 156: 157: 158: 159:
160: private function formatEmail($email, $name)
161: {
162: if (!$name && preg_match('#^(.+) +<(.*)>\z#', $email, $matches)) {
163: return array($matches[2] => $matches[1]);
164: } else {
165: return array($email => $name);
166: }
167: }
168:
169:
170: 171: 172: 173: 174:
175: public function setReturnPath($email)
176: {
177: $this->setHeader('Return-Path', $email);
178: return $this;
179: }
180:
181:
182: 183: 184: 185:
186: public function getReturnPath()
187: {
188: return $this->getHeader('Return-Path');
189: }
190:
191:
192: 193: 194: 195: 196:
197: public function setPriority($priority)
198: {
199: $this->setHeader('X-Priority', (int) $priority);
200: return $this;
201: }
202:
203:
204: 205: 206: 207:
208: public function getPriority()
209: {
210: return $this->getHeader('X-Priority');
211: }
212:
213:
214: 215: 216: 217: 218: 219:
220: public function setHtmlBody($html, $basePath = NULL)
221: {
222: if ($basePath === NULL && ($html instanceof Nette\Templating\IFileTemplate || $html instanceof Nette\Application\UI\ITemplate)) {
223: $basePath = dirname($html->getFile());
224: $bc = TRUE;
225: }
226: $html = (string) $html;
227:
228: if ($basePath) {
229: $cids = array();
230: $matches = Strings::matchAll(
231: $html,
232: '#(src\s*=\s*|background\s*=\s*|url\()(["\']?)(?![a-z]+:|[/\\#])([^"\')\s]+)#i',
233: PREG_OFFSET_CAPTURE
234: );
235: if ($matches && isset($bc)) {
236: trigger_error(__METHOD__ . '() missing second argument with image base path.', E_USER_WARNING);
237: }
238: foreach (array_reverse($matches) as $m) {
239: $file = rtrim($basePath, '/\\') . '/' . urldecode($m[3][0]);
240: if (!isset($cids[$file])) {
241: $cids[$file] = substr($this->addEmbeddedFile($file)->getHeader('Content-ID'), 1, -1);
242: }
243: $html = substr_replace($html,
244: "{$m[1][0]}{$m[2][0]}cid:{$cids[$file]}",
245: $m[0][1], strlen($m[0][0])
246: );
247: }
248: }
249:
250: if ($this->getSubject() == NULL) {
251: $html = Strings::replace($html, '#<title>(.+?)</title>#is', function($m) use (& $title) {
252: $title = $m[1];
253: });
254: $this->setSubject(html_entity_decode($title, ENT_QUOTES, 'UTF-8'));
255: }
256:
257: $this->html = ltrim(str_replace("\r", '', $html), "\n");
258:
259: if ($this->getBody() == NULL && $html != NULL) {
260: $this->setBody($this->buildText($html));
261: }
262:
263: return $this;
264: }
265:
266:
267: 268: 269: 270:
271: public function getHtmlBody()
272: {
273: return $this->html;
274: }
275:
276:
277: 278: 279: 280: 281: 282: 283:
284: public function addEmbeddedFile($file, $content = NULL, $contentType = NULL)
285: {
286: return $this->inlines[$file] = $this->createAttachment($file, $content, $contentType, 'inline')
287: ->setHeader('Content-ID', $this->getRandomId());
288: }
289:
290:
291: 292: 293: 294: 295: 296: 297:
298: public function addAttachment($file, $content = NULL, $contentType = NULL)
299: {
300: return $this->attachments[] = $this->createAttachment($file, $content, $contentType, 'attachment');
301: }
302:
303:
304: 305: 306: 307:
308: private function createAttachment($file, $content, $contentType, $disposition)
309: {
310: $part = new MimePart;
311: if ($content === NULL) {
312: $content = @file_get_contents($file);
313: if ($content === FALSE) {
314: throw new Nette\FileNotFoundException("Unable to read file '$file'.");
315: }
316: } else {
317: $content = (string) $content;
318: }
319: $part->setBody($content);
320: $part->setContentType($contentType ? $contentType : finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $content));
321: $part->setEncoding(preg_match('#(multipart|message)/#A', $contentType) ? self::ENCODING_8BIT : self::ENCODING_BASE64);
322: $part->setHeader('Content-Disposition', $disposition . '; filename="' . Strings::fixEncoding(basename($file)) . '"');
323: return $part;
324: }
325:
326:
327:
328:
329:
330: 331: 332: 333:
334: public function generateMessage()
335: {
336: return $this->build()->getEncodedMessage();
337: }
338:
339:
340: 341: 342: 343:
344: protected function build()
345: {
346: $mail = clone $this;
347: $mail->setHeader('Message-ID', $this->getRandomId());
348:
349: $cursor = $mail;
350: if ($mail->attachments) {
351: $tmp = $cursor->setContentType('multipart/mixed');
352: $cursor = $cursor->addPart();
353: foreach ($mail->attachments as $value) {
354: $tmp->addPart($value);
355: }
356: }
357:
358: if ($mail->html != NULL) {
359: $tmp = $cursor->setContentType('multipart/alternative');
360: $cursor = $cursor->addPart();
361: $alt = $tmp->addPart();
362: if ($mail->inlines) {
363: $tmp = $alt->setContentType('multipart/related');
364: $alt = $alt->addPart();
365: foreach ($mail->inlines as $value) {
366: $tmp->addPart($value);
367: }
368: }
369: $alt->setContentType('text/html', 'UTF-8')
370: ->setEncoding(preg_match('#[^\n]{990}#', $mail->html)
371: ? self::ENCODING_QUOTED_PRINTABLE
372: : (preg_match('#[\x80-\xFF]#', $mail->html) ? self::ENCODING_8BIT : self::ENCODING_7BIT))
373: ->setBody($mail->html);
374: }
375:
376: $text = $mail->getBody();
377: $mail->setBody(NULL);
378: $cursor->setContentType('text/plain', 'UTF-8')
379: ->setEncoding(preg_match('#[^\n]{990}#', $text)
380: ? self::ENCODING_QUOTED_PRINTABLE
381: : (preg_match('#[\x80-\xFF]#', $text) ? self::ENCODING_8BIT : self::ENCODING_7BIT))
382: ->setBody($text);
383:
384: return $mail;
385: }
386:
387:
388: 389: 390: 391:
392: protected function buildText($html)
393: {
394: $text = Strings::replace($html, array(
395: '#<(style|script|head).*</\\1>#Uis' => '',
396: '#<t[dh][ >]#i' => " $0",
397: '#[\r\n]+#' => ' ',
398: '#<(/?p|/?h\d|li|br|/tr)[ >/]#i' => "\n$0",
399: ));
400: $text = html_entity_decode(strip_tags($text), ENT_QUOTES, 'UTF-8');
401: $text = Strings::replace($text, '#[ \t]+#', ' ');
402: return trim($text);
403: }
404:
405:
406:
407: private function getRandomId()
408: {
409: return '<' . Nette\Utils\Random::generate() . '@'
410: . preg_replace('#[^\w.-]+#', '', isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : php_uname('n'))
411: . '>';
412: }
413:
414: }
415: