Namespaces

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Config
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
    • Templating
    • Utils
  • 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, 2011 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   string $from
 25:  * @property   string $subject
 26:  * @property   string $returnPath
 27:  * @property   int $priority
 28:  * @property   string $htmlBody
 29:  */
 30: class Message extends MimePart
 31: {
 32:     /** Priority */
 33:     const HIGH = 1,
 34:         NORMAL = 3,
 35:         LOW = 5;
 36: 
 37:     /** @var IMailer */
 38:     public static $defaultMailer = 'Nette\Mail\SendmailMailer';
 39: 
 40:     /** @var array */
 41:     public static $defaultHeaders = array(
 42:         'MIME-Version' => '1.0',
 43:         'X-Mailer' => 'Nette Framework',
 44:     );
 45: 
 46:     /** @var IMailer */
 47:     private $mailer;
 48: 
 49:     /** @var array */
 50:     private $attachments = array();
 51: 
 52:     /** @var array */
 53:     private $inlines = array();
 54: 
 55:     /** @var mixed */
 56:     private $html;
 57: 
 58:     /** @var string */
 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:      * Sets the sender of the message.
 75:      * @param  string  email or format "John Doe" <doe@example.com>
 76:      * @param  string
 77:      * @return Message  provides a fluent interface
 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:      * Returns the sender of the message.
 89:      * @return array
 90:      */
 91:     public function getFrom()
 92:     {
 93:         return $this->getHeader('From');
 94:     }
 95: 
 96: 
 97: 
 98:     /**
 99:      * Adds the reply-to address.
100:      * @param  string  email or format "John Doe" <doe@example.com>
101:      * @param  string
102:      * @return Message  provides a fluent interface
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:      * Sets the subject of the message.
114:      * @param  string
115:      * @return Message  provides a fluent interface
116:      */
117:     public function setSubject($subject)
118:     {
119:         $this->setHeader('Subject', $subject);
120:         return $this;
121:     }
122: 
123: 
124: 
125:     /**
126:      * Returns the subject of the message.
127:      * @return string
128:      */
129:     public function getSubject()
130:     {
131:         return $this->getHeader('Subject');
132:     }
133: 
134: 
135: 
136:     /**
137:      * Adds email recipient.
138:      * @param  string  email or format "John Doe" <doe@example.com>
139:      * @param  string
140:      * @return Message  provides a fluent interface
141:      */
142:     public function addTo($email, $name = NULL) // addRecipient()
143:     {
144:         $this->setHeader('To', $this->formatEmail($email, $name), TRUE);
145:         return $this;
146:     }
147: 
148: 
149: 
150:     /**
151:      * Adds carbon copy email recipient.
152:      * @param  string  email or format "John Doe" <doe@example.com>
153:      * @param  string
154:      * @return Message  provides a fluent interface
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:      * Adds blind carbon copy email recipient.
166:      * @param  string  email or format "John Doe" <doe@example.com>
167:      * @param  string
168:      * @return Message  provides a fluent interface
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:      * Formats recipient email.
180:      * @param  string
181:      * @param  string
182:      * @return array
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:      * Sets the Return-Path header of the message.
197:      * @param  string  email
198:      * @return Message  provides a fluent interface
199:      */
200:     public function setReturnPath($email)
201:     {
202:         $this->setHeader('Return-Path', $email);
203:         return $this;
204:     }
205: 
206: 
207: 
208:     /**
209:      * Returns the Return-Path header.
210:      * @return string
211:      */
212:     public function getReturnPath()
213:     {
214:         return $this->getHeader('From');
215:     }
216: 
217: 
218: 
219:     /**
220:      * Sets email priority.
221:      * @param  int
222:      * @return Message  provides a fluent interface
223:      */
224:     public function setPriority($priority)
225:     {
226:         $this->setHeader('X-Priority', (int) $priority);
227:         return $this;
228:     }
229: 
230: 
231: 
232:     /**
233:      * Returns email priority.
234:      * @return int
235:      */
236:     public function getPriority()
237:     {
238:         return $this->getHeader('X-Priority');
239:     }
240: 
241: 
242: 
243:     /**
244:      * Sets HTML body.
245:      * @param  string|Nette\Templating\ITemplate
246:      * @param  mixed base-path or FALSE to disable parsing
247:      * @return Message  provides a fluent interface
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:      * Gets HTML body.
260:      * @return mixed
261:      */
262:     public function getHtmlBody()
263:     {
264:         return $this->html;
265:     }
266: 
267: 
268: 
269:     /**
270:      * Adds embedded file.
271:      * @param  string
272:      * @param  string
273:      * @param  string
274:      * @return MimePart
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:      * Adds attachment.
286:      * @param  string
287:      * @param  string
288:      * @param  string
289:      * @return MimePart
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:      * Creates file MIME part.
300:      * @return MimePart
301:      */
302:     private function createAttachment($file, $content, $contentType, $disposition)
303:     {
304:         $part = new MimePart;
305:         if ($content === NULL) {
306:             $content = file_get_contents($file);
307:             if ($content === FALSE) {
308:                 throw new Nette\FileNotFoundException("Unable to read file '$file'.");
309:             }
310:         } else {
311:             $content = (string) $content;
312:         }
313:         $part->setBody($content);
314:         $part->setContentType($contentType ? $contentType : Nette\Utils\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="' . Strings::fixEncoding(basename($file)) . '"');
317:         return $part;
318:     }
319: 
320: 
321: 
322:     /********************* building and sending ****************d*g**/
323: 
324: 
325: 
326:     /**
327:      * Sends email.
328:      * @return void
329:      */
330:     public function send()
331:     {
332:         $this->getMailer()->send($this->build());
333:     }
334: 
335: 
336: 
337:     /**
338:      * Sets the mailer.
339:      * @param  IMailer
340:      * @return Message  provides a fluent interface
341:      */
342:     public function setMailer(IMailer $mailer)
343:     {
344:         $this->mailer = $mailer;
345:         return $this;
346:     }
347: 
348: 
349: 
350:     /**
351:      * Returns mailer.
352:      * @return IMailer
353:      */
354:     public function getMailer()
355:     {
356:         if ($this->mailer === NULL) {
357:             $this->mailer = is_object(self::$defaultMailer) ? self::$defaultMailer : new static::$defaultMailer;
358:         }
359:         return $this->mailer;
360:     }
361: 
362: 
363: 
364:     /**
365:      * Returns encoded message.
366:      * @return string
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:      * Builds email. Does not modify itself, but returns a new object.
381:      * @return Message
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) { // intentionally ==
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('#\S{990}#', $mail->html)
413:                     ? self::ENCODING_QUOTED_PRINTABLE
414:                     : (preg_match('#[\x80-\xFF]#', $mail->html) ? self::ENCODING_8BIT : self::ENCODING_7BIT))
415:                 ->setBody($mail->html);
416:         }
417: 
418:         $text = $mail->getBody();
419:         $mail->setBody(NULL);
420:         $cursor->setContentType('text/plain', 'UTF-8')
421:             ->setEncoding(preg_match('#\S{990}#', $text)
422:                 ? self::ENCODING_QUOTED_PRINTABLE
423:                 : (preg_match('#[\x80-\xFF]#', $text) ? self::ENCODING_8BIT : self::ENCODING_7BIT))
424:             ->setBody($text);
425: 
426:         return $mail;
427:     }
428: 
429: 
430: 
431:     /**
432:      * Builds HTML content.
433:      * @return void
434:      */
435:     protected function buildHtml()
436:     {
437:         if ($this->html instanceof Nette\Templating\ITemplate) {
438:             $this->html->mail = $this;
439:             if ($this->basePath === NULL && $this->html instanceof Nette\Templating\IFileTemplate) {
440:                 $this->basePath = dirname($this->html->getFile());
441:             }
442:             $this->html = $this->html->__toString(TRUE);
443:         }
444: 
445:         if ($this->basePath !== FALSE) {
446:             $cids = array();
447:             $matches = Strings::matchAll(
448:                 $this->html,
449:                 '#(src\s*=\s*|background\s*=\s*|url\()(["\'])(?![a-z]+:|[/\\#])(.+?)\\2#i',
450:                 PREG_OFFSET_CAPTURE
451:             );
452:             foreach (array_reverse($matches) as $m) {
453:                 $file = rtrim($this->basePath, '/\\') . '/' . $m[3][0];
454:                 if (!isset($cids[$file])) {
455:                     $cids[$file] = substr($this->addEmbeddedFile($file)->getHeader("Content-ID"), 1, -1);
456:                 }
457:                 $this->html = substr_replace($this->html,
458:                     "{$m[1][0]}{$m[2][0]}cid:{$cids[$file]}{$m[2][0]}",
459:                     $m[0][1], strlen($m[0][0])
460:                 );
461:             }
462:         }
463: 
464:         if (!$this->getSubject() && $matches = Strings::match($this->html, '#<title>(.+?)</title>#is')) {
465:             $this->setSubject(html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8'));
466:         }
467:     }
468: 
469: 
470: 
471:     /**
472:      * Builds text content.
473:      * @return void
474:      */
475:     protected function buildText()
476:     {
477:         $text = $this->getBody();
478:         if ($text instanceof Nette\Templating\ITemplate) {
479:             $text->mail = $this;
480:             $this->setBody($text->__toString(TRUE));
481: 
482:         } elseif ($text == NULL && $this->html != NULL) { // intentionally ==
483:             $text = Strings::replace($this->html, array(
484:                 '#<(style|script|head).*</\\1>#Uis' => '',
485:                 '#<t[dh][ >]#i' => " $0",
486:                 '#[ \t\r\n]+#' => ' ',
487:                 '#<(/?p|/?h\d|li|br|/tr)[ >/]#i' => "\n$0",
488:             ));
489:             $text = html_entity_decode(strip_tags($text), ENT_QUOTES, 'UTF-8');
490:             $this->setBody(trim($text));
491:         }
492:     }
493: 
494: 
495: 
496:     /** @return string */
497:     private function getRandomId()
498:     {
499:         return '<' . Strings::random() . '@' . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST']
500:             : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost'))
501:             . '>';
502:     }
503: 
504: }
505: 
Nette Framework 2.0beta1 API API documentation generated by ApiGen 2.3.0