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