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