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