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