Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • 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
    • Bridges
      • Nette

Classes

  • Message
  • MimePart
  • SendmailMailer
  • SmtpMailer

Interfaces

  • IMailer

Exceptions

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