1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Mail;
9:
10: use Nette,
11: Nette\Utils\Strings;
12:
13:
14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24:
25: class Message extends MimePart
26: {
27:
28: const HIGH = 1,
29: NORMAL = 3,
30: LOW = 5;
31:
32:
33: public static = array(
34: 'MIME-Version' => '1.0',
35: 'X-Mailer' => 'Nette Framework',
36: );
37:
38:
39: private $attachments = array();
40:
41:
42: private $inlines = array();
43:
44:
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: 59: 60: 61: 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: 72: 73:
74: public function getFrom()
75: {
76: return $this->getHeader('From');
77: }
78:
79:
80: 81: 82: 83: 84: 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: 95: 96: 97:
98: public function setSubject($subject)
99: {
100: $this->setHeader('Subject', $subject);
101: return $this;
102: }
103:
104:
105: 106: 107: 108:
109: public function getSubject()
110: {
111: return $this->getHeader('Subject');
112: }
113:
114:
115: 116: 117: 118: 119: 120:
121: public function addTo($email, $name = NULL)
122: {
123: $this->setHeader('To', $this->formatEmail($email, $name), TRUE);
124: return $this;
125: }
126:
127:
128: 129: 130: 131: 132: 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: 143: 144: 145: 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: 156: 157: 158: 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: 172: 173: 174:
175: public function setReturnPath($email)
176: {
177: $this->setHeader('Return-Path', $email);
178: return $this;
179: }
180:
181:
182: 183: 184: 185:
186: public function getReturnPath()
187: {
188: return $this->getHeader('Return-Path');
189: }
190:
191:
192: 193: 194: 195: 196:
197: public function setPriority($priority)
198: {
199: $this->setHeader('X-Priority', (int) $priority);
200: return $this;
201: }
202:
203:
204: 205: 206: 207:
208: public function getPriority()
209: {
210: return $this->getHeader('X-Priority');
211: }
212:
213:
214: 215: 216: 217: 218: 219:
220: public function setHtmlBody($html, $basePath = NULL)
221: {
222: if ($html instanceof Nette\Templating\ITemplate || $html instanceof Nette\Application\UI\ITemplate) {
223: $html->mail = $this;
224: if ($basePath === NULL && ($html instanceof Nette\Templating\IFileTemplate || $html instanceof Nette\Application\UI\ITemplate)) {
225: $basePath = dirname($html->getFile());
226: }
227: $html = $html->__toString(TRUE);
228: }
229:
230: if ($basePath !== FALSE) {
231: $cids = array();
232: $matches = Strings::matchAll(
233: $html,
234: '#(src\s*=\s*|background\s*=\s*|url\()(["\']?)(?![a-z]+:|[/\\#])([^"\')\s]+)#i',
235: PREG_OFFSET_CAPTURE
236: );
237: foreach (array_reverse($matches) as $m) {
238: $file = rtrim($basePath, '/\\') . '/' . $m[3][0];
239: if (!isset($cids[$file])) {
240: $cids[$file] = substr($this->addEmbeddedFile($file)->getHeader('Content-ID'), 1, -1);
241: }
242: $html = substr_replace($html,
243: "{$m[1][0]}{$m[2][0]}cid:{$cids[$file]}",
244: $m[0][1], strlen($m[0][0])
245: );
246: }
247: }
248: $this->html = $html;
249:
250: if ($this->getSubject() == NULL && $matches = Strings::match($html, '#<title>(.+?)</title>#is')) {
251: $this->setSubject(html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8'));
252: }
253:
254: if ($this->getBody() == NULL && $html != NULL) {
255: $this->setBody($this->buildText($html));
256: }
257: return $this;
258: }
259:
260:
261: 262: 263: 264:
265: public function getHtmlBody()
266: {
267: return $this->html;
268: }
269:
270:
271: 272: 273: 274: 275: 276: 277:
278: public function addEmbeddedFile($file, $content = NULL, $contentType = NULL)
279: {
280: return $this->inlines[$file] = $this->createAttachment($file, $content, $contentType, 'inline')
281: ->setHeader('Content-ID', $this->getRandomId());
282: }
283:
284:
285: 286: 287: 288: 289: 290: 291:
292: public function addAttachment($file, $content = NULL, $contentType = NULL)
293: {
294: return $this->attachments[] = $this->createAttachment($file, $content, $contentType, 'attachment');
295: }
296:
297:
298: 299: 300: 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 : finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $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:
323:
324: 325: 326: 327:
328: public function generateMessage()
329: {
330: return $this->build()->getEncodedMessage();
331: }
332:
333:
334: 335: 336: 337:
338: protected function build()
339: {
340: $mail = clone $this;
341: $mail->setHeader('Message-ID', $this->getRandomId());
342:
343: $cursor = $mail;
344: if ($mail->attachments) {
345: $tmp = $cursor->setContentType('multipart/mixed');
346: $cursor = $cursor->addPart();
347: foreach ($mail->attachments as $value) {
348: $tmp->addPart($value);
349: }
350: }
351:
352: if ($mail->html != NULL) {
353: $tmp = $cursor->setContentType('multipart/alternative');
354: $cursor = $cursor->addPart();
355: $alt = $tmp->addPart();
356: if ($mail->inlines) {
357: $tmp = $alt->setContentType('multipart/related');
358: $alt = $alt->addPart();
359: foreach ($mail->inlines as $value) {
360: $tmp->addPart($value);
361: }
362: }
363: $alt->setContentType('text/html', 'UTF-8')
364: ->setEncoding(preg_match('#\S{990}#', $mail->html)
365: ? self::ENCODING_QUOTED_PRINTABLE
366: : (preg_match('#[\x80-\xFF]#', $mail->html) ? self::ENCODING_8BIT : self::ENCODING_7BIT))
367: ->setBody($mail->html);
368: }
369:
370: $text = $mail->getBody();
371: $mail->setBody(NULL);
372: $cursor->setContentType('text/plain', 'UTF-8')
373: ->setEncoding(preg_match('#\S{990}#', $text)
374: ? self::ENCODING_QUOTED_PRINTABLE
375: : (preg_match('#[\x80-\xFF]#', $text) ? self::ENCODING_8BIT : self::ENCODING_7BIT))
376: ->setBody($text);
377:
378: return $mail;
379: }
380:
381:
382: 383: 384: 385:
386: protected function buildText($html)
387: {
388: $text = Strings::replace($html, array(
389: '#<(style|script|head).*</\\1>#Uis' => '',
390: '#<t[dh][ >]#i' => " $0",
391: '#[\r\n]+#' => ' ',
392: '#<(/?p|/?h\d|li|br|/tr)[ >/]#i' => "\n$0",
393: ));
394: $text = html_entity_decode(strip_tags($text), ENT_QUOTES, 'UTF-8');
395: $text = Strings::replace($text, '#[ \t]+#', ' ');
396: return trim($text);
397: }
398:
399:
400:
401: private function getRandomId()
402: {
403: return '<' . Nette\Utils\Random::generate() . '@'
404: . preg_replace('#[^\w.-]+#', '', isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : php_uname('n'))
405: . '>';
406: }
407:
408: }
409: