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