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