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