Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationLatte
      • ApplicationTracy
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsLatte
      • Framework
      • HttpTracy
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Templating
    • Utils
  • NetteModule
  • none
  • Tracy

Classes

  • Message
  • MimePart
  • SendmailMailer
  • SmtpMailer

Interfaces

  • IMailer

Exceptions

  • SmtpException
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Other releases
  • Nette homepage
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://nette.org)
  5:  * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  6:  */
  7: 
  8: namespace Nette\Mail;
  9: 
 10: use Nette;
 11: use Nette\Utils\Strings;
 12: 
 13: 
 14: /**
 15:  * Mail provides functionality to compose and send both text and MIME-compliant multipart email messages.
 16:  *
 17:  * @author     David Grudl
 18:  *
 19:  * @property   string $subject
 20:  * @property   mixed $htmlBody
 21:  */
 22: class Message extends MimePart
 23: {
 24:     /** Priority */
 25:     const HIGH = 1,
 26:         NORMAL = 3,
 27:         LOW = 5;
 28: 
 29:     /** @var array */
 30:     public static $defaultHeaders = array(
 31:         'MIME-Version' => '1.0',
 32:         'X-Mailer' => 'Nette Framework',
 33:     );
 34: 
 35:     /** @var array */
 36:     private $attachments = array();
 37: 
 38:     /** @var array */
 39:     private $inlines = array();
 40: 
 41:     /** @var mixed */
 42:     private $html;
 43: 
 44: 
 45:     public function __construct()
 46:     {
 47:         foreach (static::$defaultHeaders as $name => $value) {
 48:             $this->setHeader($name, $value);
 49:         }
 50:         $this->setHeader('Date', date('r'));
 51:     }
 52: 
 53: 
 54:     /**
 55:      * Sets the sender of the message.
 56:      * @param  string  email or format "John Doe" <doe@example.com>
 57:      * @param  string
 58:      * @return self
 59:      */
 60:     public function setFrom($email, $name = NULL)
 61:     {
 62:         $this->setHeader('From', $this->formatEmail($email, $name));
 63:         return $this;
 64:     }
 65: 
 66: 
 67:     /**
 68:      * Returns the sender of the message.
 69:      * @return array
 70:      */
 71:     public function getFrom()
 72:     {
 73:         return $this->getHeader('From');
 74:     }
 75: 
 76: 
 77:     /**
 78:      * Adds the reply-to address.
 79:      * @param  string  email or format "John Doe" <doe@example.com>
 80:      * @param  string
 81:      * @return self
 82:      */
 83:     public function addReplyTo($email, $name = NULL)
 84:     {
 85:         $this->setHeader('Reply-To', $this->formatEmail($email, $name), TRUE);
 86:         return $this;
 87:     }
 88: 
 89: 
 90:     /**
 91:      * Sets the subject of the message.
 92:      * @param  string
 93:      * @return self
 94:      */
 95:     public function setSubject($subject)
 96:     {
 97:         $this->setHeader('Subject', $subject);
 98:         return $this;
 99:     }
100: 
101: 
102:     /**
103:      * Returns the subject of the message.
104:      * @return string
105:      */
106:     public function getSubject()
107:     {
108:         return $this->getHeader('Subject');
109:     }
110: 
111: 
112:     /**
113:      * Adds email recipient.
114:      * @param  string  email or format "John Doe" <doe@example.com>
115:      * @param  string
116:      * @return self
117:      */
118:     public function addTo($email, $name = NULL) // addRecipient()
119:     {
120:         $this->setHeader('To', $this->formatEmail($email, $name), TRUE);
121:         return $this;
122:     }
123: 
124: 
125:     /**
126:      * Adds carbon copy email recipient.
127:      * @param  string  email or format "John Doe" <doe@example.com>
128:      * @param  string
129:      * @return self
130:      */
131:     public function addCc($email, $name = NULL)
132:     {
133:         $this->setHeader('Cc', $this->formatEmail($email, $name), TRUE);
134:         return $this;
135:     }
136: 
137: 
138:     /**
139:      * Adds blind carbon copy email recipient.
140:      * @param  string  email or format "John Doe" <doe@example.com>
141:      * @param  string
142:      * @return self
143:      */
144:     public function addBcc($email, $name = NULL)
145:     {
146:         $this->setHeader('Bcc', $this->formatEmail($email, $name), TRUE);
147:         return $this;
148:     }
149: 
150: 
151:     /**
152:      * Formats recipient email.
153:      * @param  string
154:      * @param  string
155:      * @return array
156:      */
157:     private function formatEmail($email, $name)
158:     {
159:         if (!$name && preg_match('#^(.+) +<(.*)>\z#', $email, $matches)) {
160:             return array($matches[2] => $matches[1]);
161:         } else {
162:             return array($email => $name);
163:         }
164:     }
165: 
166: 
167:     /**
168:      * Sets the Return-Path header of the message.
169:      * @param  string  email
170:      * @return self
171:      */
172:     public function setReturnPath($email)
173:     {
174:         $this->setHeader('Return-Path', $email);
175:         return $this;
176:     }
177: 
178: 
179:     /**
180:      * Returns the Return-Path header.
181:      * @return string
182:      */
183:     public function getReturnPath()
184:     {
185:         return $this->getHeader('Return-Path');
186:     }
187: 
188: 
189:     /**
190:      * Sets email priority.
191:      * @param  int
192:      * @return self
193:      */
194:     public function setPriority($priority)
195:     {
196:         $this->setHeader('X-Priority', (int) $priority);
197:         return $this;
198:     }
199: 
200: 
201:     /**
202:      * Returns email priority.
203:      * @return int
204:      */
205:     public function getPriority()
206:     {
207:         return $this->getHeader('X-Priority');
208:     }
209: 
210: 
211:     /**
212:      * Sets HTML body.
213:      * @param  string|Nette\Templating\ITemplate
214:      * @param  mixed base-path or FALSE to disable parsing
215:      * @return self
216:      */
217:     public function setHtmlBody($html, $basePath = NULL)
218:     {
219:         if ($html instanceof Nette\Templating\ITemplate || $html instanceof Nette\Application\UI\ITemplate) {
220:             $html->mail = $this;
221:             if ($basePath === NULL && ($html instanceof Nette\Templating\IFileTemplate || $html instanceof Nette\Application\UI\ITemplate)) {
222:                 $basePath = dirname($html->getFile());
223:             }
224:             $html = $html->__toString(TRUE);
225:         }
226: 
227:         if ($basePath !== FALSE) {
228:             $cids = array();
229:             $matches = Strings::matchAll(
230:                 $html,
231:                 '#(src\s*=\s*|background\s*=\s*|url\()(["\']?)(?![a-z]+:|[/\\#])([^"\')\s]+)#i',
232:                 PREG_OFFSET_CAPTURE
233:             );
234:             foreach (array_reverse($matches) as $m) {
235:                 $file = rtrim($basePath, '/\\') . '/' . urldecode($m[3][0]);
236:                 if (!isset($cids[$file])) {
237:                     $cids[$file] = substr($this->addEmbeddedFile($file)->getHeader('Content-ID'), 1, -1);
238:                 }
239:                 $html = substr_replace($html,
240:                     "{$m[1][0]}{$m[2][0]}cid:{$cids[$file]}",
241:                     $m[0][1], strlen($m[0][0])
242:                 );
243:             }
244:         }
245:         $this->html = $html;
246: 
247:         if ($this->getSubject() == NULL && $matches = Strings::match($html, '#<title>(.+?)</title>#is')) { // intentionally ==
248:             $this->setSubject(html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8'));
249:         }
250: 
251:         if ($this->getBody() == NULL && $html != NULL) { // intentionally ==
252:             $this->setBody($this->buildText($html));
253:         }
254:         return $this;
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:      * Adds embedded file.
270:      * @param  string
271:      * @param  string
272:      * @param  string
273:      * @return MimePart
274:      */
275:     public function addEmbeddedFile($file, $content = NULL, $contentType = NULL)
276:     {
277:         return $this->inlines[$file] = $this->createAttachment($file, $content, $contentType, 'inline')
278:             ->setHeader('Content-ID', $this->getRandomId());
279:     }
280: 
281: 
282:     /**
283:      * Adds attachment.
284:      * @param  string
285:      * @param  string
286:      * @param  string
287:      * @return MimePart
288:      */
289:     public function addAttachment($file, $content = NULL, $contentType = NULL)
290:     {
291:         return $this->attachments[] = $this->createAttachment($file, $content, $contentType, 'attachment');
292:     }
293: 
294: 
295:     /**
296:      * Creates file MIME part.
297:      * @return MimePart
298:      */
299:     private function createAttachment($file, $content, $contentType, $disposition)
300:     {
301:         $part = new MimePart;
302:         if ($content === NULL) {
303:             $content = @file_get_contents($file); // intentionally @
304:             if ($content === FALSE) {
305:                 throw new Nette\FileNotFoundException("Unable to read file '$file'.");
306:             }
307:         } else {
308:             $content = (string) $content;
309:         }
310:         $part->setBody($content);
311:         $part->setContentType($contentType ? $contentType : finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $content));
312:         $part->setEncoding(preg_match('#(multipart|message)/#A', $contentType) ? self::ENCODING_8BIT : self::ENCODING_BASE64);
313:         $part->setHeader('Content-Disposition', $disposition . '; filename="' . Strings::fixEncoding(basename($file)) . '"');
314:         return $part;
315:     }
316: 
317: 
318:     /********************* building and sending ****************d*g**/
319: 
320: 
321:     /**
322:      * Returns encoded message.
323:      * @return string
324:      */
325:     public function generateMessage()
326:     {
327:         return $this->build()->getEncodedMessage();
328:     }
329: 
330: 
331:     /**
332:      * Builds email. Does not modify itself, but returns a new object.
333:      * @return self
334:      */
335:     protected function build()
336:     {
337:         $mail = clone $this;
338:         $mail->setHeader('Message-ID', $this->getRandomId());
339: 
340:         $cursor = $mail;
341:         if ($mail->attachments) {
342:             $tmp = $cursor->setContentType('multipart/mixed');
343:             $cursor = $cursor->addPart();
344:             foreach ($mail->attachments as $value) {
345:                 $tmp->addPart($value);
346:             }
347:         }
348: 
349:         if ($mail->html != NULL) { // intentionally ==
350:             $tmp = $cursor->setContentType('multipart/alternative');
351:             $cursor = $cursor->addPart();
352:             $alt = $tmp->addPart();
353:             if ($mail->inlines) {
354:                 $tmp = $alt->setContentType('multipart/related');
355:                 $alt = $alt->addPart();
356:                 foreach ($mail->inlines as $value) {
357:                     $tmp->addPart($value);
358:                 }
359:             }
360:             $alt->setContentType('text/html', 'UTF-8')
361:                 ->setEncoding(preg_match('#[^\n]{990}#', $mail->html)
362:                     ? self::ENCODING_QUOTED_PRINTABLE
363:                     : (preg_match('#[\x80-\xFF]#', $mail->html) ? self::ENCODING_8BIT : self::ENCODING_7BIT))
364:                 ->setBody($mail->html);
365:         }
366: 
367:         $text = $mail->getBody();
368:         $mail->setBody(NULL);
369:         $cursor->setContentType('text/plain', 'UTF-8')
370:             ->setEncoding(preg_match('#[^\n]{990}#', $text)
371:                 ? self::ENCODING_QUOTED_PRINTABLE
372:                 : (preg_match('#[\x80-\xFF]#', $text) ? self::ENCODING_8BIT : self::ENCODING_7BIT))
373:             ->setBody($text);
374: 
375:         return $mail;
376:     }
377: 
378: 
379:     /**
380:      * Builds text content.
381:      * @return string
382:      */
383:     protected function buildText($html)
384:     {
385:         $text = Strings::replace($html, array(
386:             '#<(style|script|head).*</\\1>#Uis' => '',
387:             '#<t[dh][ >]#i' => ' $0',
388:             '#[\r\n]+#' => ' ',
389:             '#<(/?p|/?h\d|li|br|/tr)[ >/]#i' => "\n$0",
390:         ));
391:         $text = html_entity_decode(strip_tags($text), ENT_QUOTES, 'UTF-8');
392:         $text = Strings::replace($text, '#[ \t]+#', ' ');
393:         return trim($text);
394:     }
395: 
396: 
397:     /** @return string */
398:     private function getRandomId()
399:     {
400:         return '<' . Nette\Utils\Random::generate() . '@'
401:             . preg_replace('#[^\w.-]+#', '', isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : php_uname('n'))
402:             . '>';
403:     }
404: 
405: }
406: 
Nette 2.2 API documentation generated by ApiGen 2.8.0