Namespaces

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Config
      • Adapters
      • Extensions
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Diagnostics
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
      • PhpGenerator
  • NetteModule
  • None
  • PHP

Classes

  • Message
  • MimePart
  • SendmailMailer
  • SmtpMailer

Interfaces

  • IMailer

Exceptions

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