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