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