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: class Message extends MimePart
23: {
24:
25: const HIGH = 1,
26: NORMAL = 3,
27: LOW = 5;
28:
29:
30: public static = array(
31: 'MIME-Version' => '1.0',
32: 'X-Mailer' => 'Nette Framework',
33: );
34:
35:
36: private $attachments = array();
37:
38:
39: private $inlines = array();
40:
41:
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: 56: 57: 58: 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: 69: 70:
71: public function getFrom()
72: {
73: return $this->getHeader('From');
74: }
75:
76:
77: 78: 79: 80: 81: 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: 92: 93: 94:
95: public function setSubject($subject)
96: {
97: $this->setHeader('Subject', $subject);
98: return $this;
99: }
100:
101:
102: 103: 104: 105:
106: public function getSubject()
107: {
108: return $this->getHeader('Subject');
109: }
110:
111:
112: 113: 114: 115: 116: 117:
118: public function addTo($email, $name = NULL)
119: {
120: $this->setHeader('To', $this->formatEmail($email, $name), TRUE);
121: return $this;
122: }
123:
124:
125: 126: 127: 128: 129: 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: 140: 141: 142: 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: 153: 154: 155: 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: 169: 170: 171:
172: public function setReturnPath($email)
173: {
174: $this->setHeader('Return-Path', $email);
175: return $this;
176: }
177:
178:
179: 180: 181: 182:
183: public function getReturnPath()
184: {
185: return $this->getHeader('Return-Path');
186: }
187:
188:
189: 190: 191: 192: 193:
194: public function setPriority($priority)
195: {
196: $this->setHeader('X-Priority', (int) $priority);
197: return $this;
198: }
199:
200:
201: 202: 203: 204:
205: public function getPriority()
206: {
207: return $this->getHeader('X-Priority');
208: }
209:
210:
211: 212: 213: 214: 215: 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')) {
248: $this->setSubject(html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8'));
249: }
250:
251: if ($this->getBody() == NULL && $html != NULL) {
252: $this->setBody($this->buildText($html));
253: }
254: return $this;
255: }
256:
257:
258: 259: 260: 261:
262: public function getHtmlBody()
263: {
264: return $this->html;
265: }
266:
267:
268: 269: 270: 271: 272: 273: 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: 284: 285: 286: 287: 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: 297: 298:
299: private function createAttachment($file, $content, $contentType, $disposition)
300: {
301: $part = new MimePart;
302: if ($content === NULL) {
303: $content = @file_get_contents($file);
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:
319:
320:
321: 322: 323: 324:
325: public function generateMessage()
326: {
327: return $this->build()->getEncodedMessage();
328: }
329:
330:
331: 332: 333: 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) {
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: 381: 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:
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: