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