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