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