Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
12345 anikendra 1
<?php
2
/**
3
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
4
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
5
 *
6
 * Licensed under The MIT License
7
 * For full copyright and license information, please see the LICENSE.txt
8
 * Redistributions of files must retain the above copyright notice.
9
 *
10
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
11
 * @link          http://cakephp.org CakePHP(tm) Project
12
 * @package       Cake.Network.Email
13
 * @since         CakePHP(tm) v 2.0.0
14
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
15
 */
16
 
17
App::uses('Multibyte', 'I18n');
18
App::uses('AbstractTransport', 'Network/Email');
19
App::uses('File', 'Utility');
20
App::uses('String', 'Utility');
21
App::uses('View', 'View');
22
 
23
/**
24
 * CakePHP email class.
25
 *
26
 * This class is used for handling Internet Message Format based
27
 * based on the standard outlined in http://www.rfc-editor.org/rfc/rfc2822.txt
28
 *
29
 * @package       Cake.Network.Email
30
 */
31
class CakeEmail {
32
 
33
/**
34
 * Default X-Mailer
35
 *
36
 * @var string
37
 */
38
	const EMAIL_CLIENT = 'CakePHP Email';
39
 
40
/**
41
 * Line length - no should more - RFC 2822 - 2.1.1
42
 *
43
 * @var int
44
 */
45
	const LINE_LENGTH_SHOULD = 78;
46
 
47
/**
48
 * Line length - no must more - RFC 2822 - 2.1.1
49
 *
50
 * @var int
51
 */
52
	const LINE_LENGTH_MUST = 998;
53
 
54
/**
55
 * Type of message - HTML
56
 *
57
 * @var string
58
 */
59
	const MESSAGE_HTML = 'html';
60
 
61
/**
62
 * Type of message - TEXT
63
 *
64
 * @var string
65
 */
66
	const MESSAGE_TEXT = 'text';
67
 
68
/**
69
 * Recipient of the email
70
 *
71
 * @var array
72
 */
73
	protected $_to = array();
74
 
75
/**
76
 * The mail which the email is sent from
77
 *
78
 * @var array
79
 */
80
	protected $_from = array();
81
 
82
/**
83
 * The sender email
84
 *
85
 * @var array
86
 */
87
	protected $_sender = array();
88
 
89
/**
90
 * The email the recipient will reply to
91
 *
92
 * @var array
93
 */
94
	protected $_replyTo = array();
95
 
96
/**
97
 * The read receipt email
98
 *
99
 * @var array
100
 */
101
	protected $_readReceipt = array();
102
 
103
/**
104
 * The mail that will be used in case of any errors like
105
 * - Remote mailserver down
106
 * - Remote user has exceeded his quota
107
 * - Unknown user
108
 *
109
 * @var array
110
 */
111
	protected $_returnPath = array();
112
 
113
/**
114
 * Carbon Copy
115
 *
116
 * List of email's that should receive a copy of the email.
117
 * The Recipient WILL be able to see this list
118
 *
119
 * @var array
120
 */
121
	protected $_cc = array();
122
 
123
/**
124
 * Blind Carbon Copy
125
 *
126
 * List of email's that should receive a copy of the email.
127
 * The Recipient WILL NOT be able to see this list
128
 *
129
 * @var array
130
 */
131
	protected $_bcc = array();
132
 
133
/**
134
 * Message ID
135
 *
136
 * @var bool|string
137
 */
138
	protected $_messageId = true;
139
 
140
/**
141
 * Domain for messageId generation.
142
 * Needs to be manually set for CLI mailing as env('HTTP_HOST') is empty
143
 *
144
 * @var string
145
 */
146
	protected $_domain = null;
147
 
148
/**
149
 * The subject of the email
150
 *
151
 * @var string
152
 */
153
	protected $_subject = '';
154
 
155
/**
156
 * Associative array of a user defined headers
157
 * Keys will be prefixed 'X-' as per RFC2822 Section 4.7.5
158
 *
159
 * @var array
160
 */
161
	protected $_headers = array();
162
 
163
/**
164
 * Layout for the View
165
 *
166
 * @var string
167
 */
168
	protected $_layout = 'default';
169
 
170
/**
171
 * Template for the view
172
 *
173
 * @var string
174
 */
175
	protected $_template = '';
176
 
177
/**
178
 * View for render
179
 *
180
 * @var string
181
 */
182
	protected $_viewRender = 'View';
183
 
184
/**
185
 * Vars to sent to render
186
 *
187
 * @var array
188
 */
189
	protected $_viewVars = array();
190
 
191
/**
192
 * Theme for the View
193
 *
194
 * @var array
195
 */
196
	protected $_theme = null;
197
 
198
/**
199
 * Helpers to be used in the render
200
 *
201
 * @var array
202
 */
203
	protected $_helpers = array('Html');
204
 
205
/**
206
 * Text message
207
 *
208
 * @var string
209
 */
210
	protected $_textMessage = '';
211
 
212
/**
213
 * Html message
214
 *
215
 * @var string
216
 */
217
	protected $_htmlMessage = '';
218
 
219
/**
220
 * Final message to send
221
 *
222
 * @var array
223
 */
224
	protected $_message = array();
225
 
226
/**
227
 * Available formats to be sent.
228
 *
229
 * @var array
230
 */
231
	protected $_emailFormatAvailable = array('text', 'html', 'both');
232
 
233
/**
234
 * What format should the email be sent in
235
 *
236
 * @var string
237
 */
238
	protected $_emailFormat = 'text';
239
 
240
/**
241
 * What method should the email be sent
242
 *
243
 * @var string
244
 */
245
	protected $_transportName = 'Mail';
246
 
247
/**
248
 * Instance of transport class
249
 *
250
 * @var AbstractTransport
251
 */
252
	protected $_transportClass = null;
253
 
254
/**
255
 * Charset the email body is sent in
256
 *
257
 * @var string
258
 */
259
	public $charset = 'utf-8';
260
 
261
/**
262
 * Charset the email header is sent in
263
 * If null, the $charset property will be used as default
264
 *
265
 * @var string
266
 */
267
	public $headerCharset = null;
268
 
269
/**
270
 * The application wide charset, used to encode headers and body
271
 *
272
 * @var string
273
 */
274
	protected $_appCharset = null;
275
 
276
/**
277
 * List of files that should be attached to the email.
278
 *
279
 * Only absolute paths
280
 *
281
 * @var array
282
 */
283
	protected $_attachments = array();
284
 
285
/**
286
 * If set, boundary to use for multipart mime messages
287
 *
288
 * @var string
289
 */
290
	protected $_boundary = null;
291
 
292
/**
293
 * Configuration to transport
294
 *
295
 * @var string|array
296
 */
297
	protected $_config = array();
298
 
299
/**
300
 * 8Bit character sets
301
 *
302
 * @var array
303
 */
304
	protected $_charset8bit = array('UTF-8', 'SHIFT_JIS');
305
 
306
/**
307
 * Define Content-Type charset name
308
 *
309
 * @var array
310
 */
311
	protected $_contentTypeCharset = array(
312
		'ISO-2022-JP-MS' => 'ISO-2022-JP'
313
	);
314
 
315
/**
316
 * Regex for email validation
317
 *
318
 * If null, filter_var() will be used. Use the emailPattern() method
319
 * to set a custom pattern.'
320
 *
321
 * @var string
322
 */
323
	protected $_emailPattern = '/^((?:[\p{L}0-9.!#$%&\'*+\/=?^_`{|}~-]+)*@[\p{L}0-9-.]+)$/ui';
324
 
325
/**
326
 * The class name used for email configuration.
327
 *
328
 * @var string
329
 */
330
	protected $_configClass = 'EmailConfig';
331
 
332
/**
333
 * Constructor
334
 *
335
 * @param array|string $config Array of configs, or string to load configs from email.php
336
 */
337
	public function __construct($config = null) {
338
		$this->_appCharset = Configure::read('App.encoding');
339
		if ($this->_appCharset !== null) {
340
			$this->charset = $this->_appCharset;
341
		}
342
		$this->_domain = preg_replace('/\:\d+$/', '', env('HTTP_HOST'));
343
		if (empty($this->_domain)) {
344
			$this->_domain = php_uname('n');
345
		}
346
 
347
		if ($config) {
348
			$this->config($config);
349
		}
350
		if (empty($this->headerCharset)) {
351
			$this->headerCharset = $this->charset;
352
		}
353
	}
354
 
355
/**
356
 * From
357
 *
358
 * @param string|array $email Null to get, String with email,
359
 *   Array with email as key, name as value or email as value (without name)
360
 * @param string $name Name
361
 * @return array|CakeEmail
362
 * @throws SocketException
363
 */
364
	public function from($email = null, $name = null) {
365
		if ($email === null) {
366
			return $this->_from;
367
		}
368
		return $this->_setEmailSingle('_from', $email, $name, __d('cake_dev', 'From requires only 1 email address.'));
369
	}
370
 
371
/**
372
 * Sender
373
 *
374
 * @param string|array $email Null to get, String with email,
375
 *   Array with email as key, name as value or email as value (without name)
376
 * @param string $name Name
377
 * @return array|CakeEmail
378
 * @throws SocketException
379
 */
380
	public function sender($email = null, $name = null) {
381
		if ($email === null) {
382
			return $this->_sender;
383
		}
384
		return $this->_setEmailSingle('_sender', $email, $name, __d('cake_dev', 'Sender requires only 1 email address.'));
385
	}
386
 
387
/**
388
 * Reply-To
389
 *
390
 * @param string|array $email Null to get, String with email,
391
 *   Array with email as key, name as value or email as value (without name)
392
 * @param string $name Name
393
 * @return array|CakeEmail
394
 * @throws SocketException
395
 */
396
	public function replyTo($email = null, $name = null) {
397
		if ($email === null) {
398
			return $this->_replyTo;
399
		}
400
		return $this->_setEmailSingle('_replyTo', $email, $name, __d('cake_dev', 'Reply-To requires only 1 email address.'));
401
	}
402
 
403
/**
404
 * Read Receipt (Disposition-Notification-To header)
405
 *
406
 * @param string|array $email Null to get, String with email,
407
 *   Array with email as key, name as value or email as value (without name)
408
 * @param string $name Name
409
 * @return array|CakeEmail
410
 * @throws SocketException
411
 */
412
	public function readReceipt($email = null, $name = null) {
413
		if ($email === null) {
414
			return $this->_readReceipt;
415
		}
416
		return $this->_setEmailSingle('_readReceipt', $email, $name, __d('cake_dev', 'Disposition-Notification-To requires only 1 email address.'));
417
	}
418
 
419
/**
420
 * Return Path
421
 *
422
 * @param string|array $email Null to get, String with email,
423
 *   Array with email as key, name as value or email as value (without name)
424
 * @param string $name Name
425
 * @return array|CakeEmail
426
 * @throws SocketException
427
 */
428
	public function returnPath($email = null, $name = null) {
429
		if ($email === null) {
430
			return $this->_returnPath;
431
		}
432
		return $this->_setEmailSingle('_returnPath', $email, $name, __d('cake_dev', 'Return-Path requires only 1 email address.'));
433
	}
434
 
435
/**
436
 * To
437
 *
438
 * @param string|array $email Null to get, String with email,
439
 *   Array with email as key, name as value or email as value (without name)
440
 * @param string $name Name
441
 * @return array|CakeEmail
442
 */
443
	public function to($email = null, $name = null) {
444
		if ($email === null) {
445
			return $this->_to;
446
		}
447
		return $this->_setEmail('_to', $email, $name);
448
	}
449
 
450
/**
451
 * Add To
452
 *
453
 * @param string|array $email Null to get, String with email,
454
 *   Array with email as key, name as value or email as value (without name)
455
 * @param string $name Name
456
 * @return $this
457
 */
458
	public function addTo($email, $name = null) {
459
		return $this->_addEmail('_to', $email, $name);
460
	}
461
 
462
/**
463
 * Cc
464
 *
465
 * @param string|array $email Null to get, String with email,
466
 *   Array with email as key, name as value or email as value (without name)
467
 * @param string $name Name
468
 * @return array|CakeEmail
469
 */
470
	public function cc($email = null, $name = null) {
471
		if ($email === null) {
472
			return $this->_cc;
473
		}
474
		return $this->_setEmail('_cc', $email, $name);
475
	}
476
 
477
/**
478
 * Add Cc
479
 *
480
 * @param string|array $email Null to get, String with email,
481
 *   Array with email as key, name as value or email as value (without name)
482
 * @param string $name Name
483
 * @return $this
484
 */
485
	public function addCc($email, $name = null) {
486
		return $this->_addEmail('_cc', $email, $name);
487
	}
488
 
489
/**
490
 * Bcc
491
 *
492
 * @param string|array $email Null to get, String with email,
493
 *   Array with email as key, name as value or email as value (without name)
494
 * @param string $name Name
495
 * @return array|CakeEmail
496
 */
497
	public function bcc($email = null, $name = null) {
498
		if ($email === null) {
499
			return $this->_bcc;
500
		}
501
		return $this->_setEmail('_bcc', $email, $name);
502
	}
503
 
504
/**
505
 * Add Bcc
506
 *
507
 * @param string|array $email Null to get, String with email,
508
 *   Array with email as key, name as value or email as value (without name)
509
 * @param string $name Name
510
 * @return $this
511
 */
512
	public function addBcc($email, $name = null) {
513
		return $this->_addEmail('_bcc', $email, $name);
514
	}
515
 
516
/**
517
 * Charset setter/getter
518
 *
519
 * @param string $charset Character set.
520
 * @return string this->charset
521
 */
522
	public function charset($charset = null) {
523
		if ($charset === null) {
524
			return $this->charset;
525
		}
526
		$this->charset = $charset;
527
		if (empty($this->headerCharset)) {
528
			$this->headerCharset = $charset;
529
		}
530
		return $this->charset;
531
	}
532
 
533
/**
534
 * HeaderCharset setter/getter
535
 *
536
 * @param string $charset Character set.
537
 * @return string this->charset
538
 */
539
	public function headerCharset($charset = null) {
540
		if ($charset === null) {
541
			return $this->headerCharset;
542
		}
543
		return $this->headerCharset = $charset;
544
	}
545
 
546
/**
547
 * EmailPattern setter/getter
548
 *
549
 * @param string $regex for email address validation
550
 * @return string|$this
551
 */
552
	public function emailPattern($regex = false) {
553
		if ($regex === false) {
554
			return $this->_emailPattern;
555
		}
556
		$this->_emailPattern = $regex;
557
		return $this;
558
	}
559
 
560
/**
561
 * Set email
562
 *
563
 * @param string $varName Property name
564
 * @param string|array $email String with email,
565
 *   Array with email as key, name as value or email as value (without name)
566
 * @param string $name Name
567
 * @return $this
568
 */
569
	protected function _setEmail($varName, $email, $name) {
570
		if (!is_array($email)) {
571
			$this->_validateEmail($email);
572
			if ($name === null) {
573
				$name = $email;
574
			}
575
			$this->{$varName} = array($email => $name);
576
			return $this;
577
		}
578
		$list = array();
579
		foreach ($email as $key => $value) {
580
			if (is_int($key)) {
581
				$key = $value;
582
			}
583
			$this->_validateEmail($key);
584
			$list[$key] = $value;
585
		}
586
		$this->{$varName} = $list;
587
		return $this;
588
	}
589
 
590
/**
591
 * Validate email address
592
 *
593
 * @param string $email Email
594
 * @return void
595
 * @throws SocketException If email address does not validate
596
 */
597
	protected function _validateEmail($email) {
598
		if ($this->_emailPattern === null && filter_var($email, FILTER_VALIDATE_EMAIL)) {
599
			return;
600
		} elseif (preg_match($this->_emailPattern, $email)) {
601
			return;
602
		}
603
		throw new SocketException(__d('cake_dev', 'Invalid email: "%s"', $email));
604
	}
605
 
606
/**
607
 * Set only 1 email
608
 *
609
 * @param string $varName Property name
610
 * @param string|array $email String with email,
611
 *   Array with email as key, name as value or email as value (without name)
612
 * @param string $name Name
613
 * @param string $throwMessage Exception message
614
 * @return $this
615
 * @throws SocketException
616
 */
617
	protected function _setEmailSingle($varName, $email, $name, $throwMessage) {
618
		$current = $this->{$varName};
619
		$this->_setEmail($varName, $email, $name);
620
		if (count($this->{$varName}) !== 1) {
621
			$this->{$varName} = $current;
622
			throw new SocketException($throwMessage);
623
		}
624
		return $this;
625
	}
626
 
627
/**
628
 * Add email
629
 *
630
 * @param string $varName Property name
631
 * @param string|array $email String with email,
632
 *   Array with email as key, name as value or email as value (without name)
633
 * @param string $name Name
634
 * @return $this
635
 * @throws SocketException
636
 */
637
	protected function _addEmail($varName, $email, $name) {
638
		if (!is_array($email)) {
639
			$this->_validateEmail($email);
640
			if ($name === null) {
641
				$name = $email;
642
			}
643
			$this->{$varName}[$email] = $name;
644
			return $this;
645
		}
646
		$list = array();
647
		foreach ($email as $key => $value) {
648
			if (is_int($key)) {
649
				$key = $value;
650
			}
651
			$this->_validateEmail($key);
652
			$list[$key] = $value;
653
		}
654
		$this->{$varName} = array_merge($this->{$varName}, $list);
655
		return $this;
656
	}
657
 
658
/**
659
 * Get/Set Subject.
660
 *
661
 * @param string $subject Subject string.
662
 * @return string|$this
663
 */
664
	public function subject($subject = null) {
665
		if ($subject === null) {
666
			return $this->_subject;
667
		}
668
		$this->_subject = $this->_encode((string)$subject);
669
		return $this;
670
	}
671
 
672
/**
673
 * Sets headers for the message
674
 *
675
 * @param array $headers Associative array containing headers to be set.
676
 * @return $this
677
 * @throws SocketException
678
 */
679
	public function setHeaders($headers) {
680
		if (!is_array($headers)) {
681
			throw new SocketException(__d('cake_dev', '$headers should be an array.'));
682
		}
683
		$this->_headers = $headers;
684
		return $this;
685
	}
686
 
687
/**
688
 * Add header for the message
689
 *
690
 * @param array $headers Headers to set.
691
 * @return $this
692
 * @throws SocketException
693
 */
694
	public function addHeaders($headers) {
695
		if (!is_array($headers)) {
696
			throw new SocketException(__d('cake_dev', '$headers should be an array.'));
697
		}
698
		$this->_headers = array_merge($this->_headers, $headers);
699
		return $this;
700
	}
701
 
702
/**
703
 * Get list of headers
704
 *
705
 * ### Includes:
706
 *
707
 * - `from`
708
 * - `replyTo`
709
 * - `readReceipt`
710
 * - `returnPath`
711
 * - `to`
712
 * - `cc`
713
 * - `bcc`
714
 * - `subject`
715
 *
716
 * @param array $include List of headers.
717
 * @return array
718
 */
719
	public function getHeaders($include = array()) {
720
		if ($include == array_values($include)) {
721
			$include = array_fill_keys($include, true);
722
		}
723
		$defaults = array_fill_keys(
724
			array(
725
				'from', 'sender', 'replyTo', 'readReceipt', 'returnPath',
726
				'to', 'cc', 'bcc', 'subject'),
727
			false
728
		);
729
		$include += $defaults;
730
 
731
		$headers = array();
732
		$relation = array(
733
			'from' => 'From',
734
			'replyTo' => 'Reply-To',
735
			'readReceipt' => 'Disposition-Notification-To',
736
			'returnPath' => 'Return-Path'
737
		);
738
		foreach ($relation as $var => $header) {
739
			if ($include[$var]) {
740
				$var = '_' . $var;
741
				$headers[$header] = current($this->_formatAddress($this->{$var}));
742
			}
743
		}
744
		if ($include['sender']) {
745
			if (key($this->_sender) === key($this->_from)) {
746
				$headers['Sender'] = '';
747
			} else {
748
				$headers['Sender'] = current($this->_formatAddress($this->_sender));
749
			}
750
		}
751
 
752
		foreach (array('to', 'cc', 'bcc') as $var) {
753
			if ($include[$var]) {
754
				$classVar = '_' . $var;
755
				$headers[ucfirst($var)] = implode(', ', $this->_formatAddress($this->{$classVar}));
756
			}
757
		}
758
 
759
		$headers += $this->_headers;
760
		if (!isset($headers['X-Mailer'])) {
761
			$headers['X-Mailer'] = self::EMAIL_CLIENT;
762
		}
763
		if (!isset($headers['Date'])) {
764
			$headers['Date'] = date(DATE_RFC2822);
765
		}
766
		if ($this->_messageId !== false) {
767
			if ($this->_messageId === true) {
768
				$headers['Message-ID'] = '<' . str_replace('-', '', String::UUID()) . '@' . $this->_domain . '>';
769
			} else {
770
				$headers['Message-ID'] = $this->_messageId;
771
			}
772
		}
773
 
774
		if ($include['subject']) {
775
			$headers['Subject'] = $this->_subject;
776
		}
777
 
778
		$headers['MIME-Version'] = '1.0';
779
		if (!empty($this->_attachments)) {
780
			$headers['Content-Type'] = 'multipart/mixed; boundary="' . $this->_boundary . '"';
781
		} elseif ($this->_emailFormat === 'both') {
782
			$headers['Content-Type'] = 'multipart/alternative; boundary="' . $this->_boundary . '"';
783
		} elseif ($this->_emailFormat === 'text') {
784
			$headers['Content-Type'] = 'text/plain; charset=' . $this->_getContentTypeCharset();
785
		} elseif ($this->_emailFormat === 'html') {
786
			$headers['Content-Type'] = 'text/html; charset=' . $this->_getContentTypeCharset();
787
		}
788
		$headers['Content-Transfer-Encoding'] = $this->_getContentTransferEncoding();
789
 
790
		return $headers;
791
	}
792
 
793
/**
794
 * Format addresses
795
 *
796
 * If the address contains non alphanumeric/whitespace characters, it will
797
 * be quoted as characters like `:` and `,` are known to cause issues
798
 * in address header fields.
799
 *
800
 * @param array $address Addresses to format.
801
 * @return array
802
 */
803
	protected function _formatAddress($address) {
804
		$return = array();
805
		foreach ($address as $email => $alias) {
806
			if ($email === $alias) {
807
				$return[] = $email;
808
			} else {
809
				$encoded = $this->_encode($alias);
810
				if ($encoded === $alias && preg_match('/[^a-z0-9 ]/i', $encoded)) {
811
					$encoded = '"' . str_replace('"', '\"', $encoded) . '"';
812
				}
813
				$return[] = sprintf('%s <%s>', $encoded, $email);
814
			}
815
		}
816
		return $return;
817
	}
818
 
819
/**
820
 * Template and layout
821
 *
822
 * @param bool|string $template Template name or null to not use
823
 * @param bool|string $layout Layout name or null to not use
824
 * @return array|$this
825
 */
826
	public function template($template = false, $layout = false) {
827
		if ($template === false) {
828
			return array(
829
				'template' => $this->_template,
830
				'layout' => $this->_layout
831
			);
832
		}
833
		$this->_template = $template;
834
		if ($layout !== false) {
835
			$this->_layout = $layout;
836
		}
837
		return $this;
838
	}
839
 
840
/**
841
 * View class for render
842
 *
843
 * @param string $viewClass View class name.
844
 * @return string|$this
845
 */
846
	public function viewRender($viewClass = null) {
847
		if ($viewClass === null) {
848
			return $this->_viewRender;
849
		}
850
		$this->_viewRender = $viewClass;
851
		return $this;
852
	}
853
 
854
/**
855
 * Variables to be set on render
856
 *
857
 * @param array $viewVars Variables to set for view.
858
 * @return array|$this
859
 */
860
	public function viewVars($viewVars = null) {
861
		if ($viewVars === null) {
862
			return $this->_viewVars;
863
		}
864
		$this->_viewVars = array_merge($this->_viewVars, (array)$viewVars);
865
		return $this;
866
	}
867
 
868
/**
869
 * Theme to use when rendering
870
 *
871
 * @param string $theme Theme name.
872
 * @return string|$this
873
 */
874
	public function theme($theme = null) {
875
		if ($theme === null) {
876
			return $this->_theme;
877
		}
878
		$this->_theme = $theme;
879
		return $this;
880
	}
881
 
882
/**
883
 * Helpers to be used in render
884
 *
885
 * @param array $helpers Helpers list.
886
 * @return array|$this
887
 */
888
	public function helpers($helpers = null) {
889
		if ($helpers === null) {
890
			return $this->_helpers;
891
		}
892
		$this->_helpers = (array)$helpers;
893
		return $this;
894
	}
895
 
896
/**
897
 * Email format
898
 *
899
 * @param string $format Formatting string.
900
 * @return string|$this
901
 * @throws SocketException
902
 */
903
	public function emailFormat($format = null) {
904
		if ($format === null) {
905
			return $this->_emailFormat;
906
		}
907
		if (!in_array($format, $this->_emailFormatAvailable)) {
908
			throw new SocketException(__d('cake_dev', 'Format not available.'));
909
		}
910
		$this->_emailFormat = $format;
911
		return $this;
912
	}
913
 
914
/**
915
 * Transport name
916
 *
917
 * @param string $name Transport name.
918
 * @return string|$this
919
 */
920
	public function transport($name = null) {
921
		if ($name === null) {
922
			return $this->_transportName;
923
		}
924
		$this->_transportName = (string)$name;
925
		$this->_transportClass = null;
926
		return $this;
927
	}
928
 
929
/**
930
 * Return the transport class
931
 *
932
 * @return AbstractTransport
933
 * @throws SocketException
934
 */
935
	public function transportClass() {
936
		if ($this->_transportClass) {
937
			return $this->_transportClass;
938
		}
939
		list($plugin, $transportClassname) = pluginSplit($this->_transportName, true);
940
		$transportClassname .= 'Transport';
941
		App::uses($transportClassname, $plugin . 'Network/Email');
942
		if (!class_exists($transportClassname)) {
943
			throw new SocketException(__d('cake_dev', 'Class "%s" not found.', $transportClassname));
944
		} elseif (!method_exists($transportClassname, 'send')) {
945
			throw new SocketException(__d('cake_dev', 'The "%s" does not have a %s method.', $transportClassname, 'send()'));
946
		}
947
 
948
		return $this->_transportClass = new $transportClassname();
949
	}
950
 
951
/**
952
 * Message-ID
953
 *
954
 * @param bool|string $message True to generate a new Message-ID, False to ignore (not send in email), String to set as Message-ID
955
 * @return bool|string|$this
956
 * @throws SocketException
957
 */
958
	public function messageId($message = null) {
959
		if ($message === null) {
960
			return $this->_messageId;
961
		}
962
		if (is_bool($message)) {
963
			$this->_messageId = $message;
964
		} else {
965
			if (!preg_match('/^\<.+@.+\>$/', $message)) {
966
				throw new SocketException(__d('cake_dev', 'Invalid format for Message-ID. The text should be something like "<uuid@server.com>"'));
967
			}
968
			$this->_messageId = $message;
969
		}
970
		return $this;
971
	}
972
 
973
/**
974
 * Domain as top level (the part after @)
975
 *
976
 * @param string $domain Manually set the domain for CLI mailing
977
 * @return string|$this
978
 */
979
	public function domain($domain = null) {
980
		if ($domain === null) {
981
			return $this->_domain;
982
		}
983
		$this->_domain = $domain;
984
		return $this;
985
	}
986
 
987
/**
988
 * Add attachments to the email message
989
 *
990
 * Attachments can be defined in a few forms depending on how much control you need:
991
 *
992
 * Attach a single file:
993
 *
994
 * {{{
995
 * $email->attachments('path/to/file');
996
 * }}}
997
 *
998
 * Attach a file with a different filename:
999
 *
1000
 * {{{
1001
 * $email->attachments(array('custom_name.txt' => 'path/to/file.txt'));
1002
 * }}}
1003
 *
1004
 * Attach a file and specify additional properties:
1005
 *
1006
 * {{{
1007
 * $email->attachments(array('custom_name.png' => array(
1008
 *		'file' => 'path/to/file',
1009
 *		'mimetype' => 'image/png',
1010
 *		'contentId' => 'abc123',
1011
 *		'contentDisposition' => false
1012
 * ));
1013
 * }}}
1014
 *
1015
 * Attach a file from string and specify additional properties:
1016
 *
1017
 * {{{
1018
 * $email->attachments(array('custom_name.png' => array(
1019
 *		'data' => file_get_contents('path/to/file'),
1020
 *		'mimetype' => 'image/png'
1021
 * ));
1022
 * }}}
1023
 *
1024
 * The `contentId` key allows you to specify an inline attachment. In your email text, you
1025
 * can use `<img src="cid:abc123" />` to display the image inline.
1026
 *
1027
 * The `contentDisposition` key allows you to disable the `Content-Disposition` header, this can improve
1028
 * attachment compatibility with outlook email clients.
1029
 *
1030
 * @param string|array $attachments String with the filename or array with filenames
1031
 * @return array|$this Either the array of attachments when getting or $this when setting.
1032
 * @throws SocketException
1033
 */
1034
	public function attachments($attachments = null) {
1035
		if ($attachments === null) {
1036
			return $this->_attachments;
1037
		}
1038
		$attach = array();
1039
		foreach ((array)$attachments as $name => $fileInfo) {
1040
			if (!is_array($fileInfo)) {
1041
				$fileInfo = array('file' => $fileInfo);
1042
			}
1043
			if (!isset($fileInfo['file'])) {
1044
				if (!isset($fileInfo['data'])) {
1045
					throw new SocketException(__d('cake_dev', 'No file or data specified.'));
1046
				}
1047
				if (is_int($name)) {
1048
					throw new SocketException(__d('cake_dev', 'No filename specified.'));
1049
				}
1050
				$fileInfo['data'] = chunk_split(base64_encode($fileInfo['data']), 76, "\r\n");
1051
			} else {
1052
				$fileName = $fileInfo['file'];
1053
				$fileInfo['file'] = realpath($fileInfo['file']);
1054
				if ($fileInfo['file'] === false || !file_exists($fileInfo['file'])) {
1055
					throw new SocketException(__d('cake_dev', 'File not found: "%s"', $fileName));
1056
				}
1057
				if (is_int($name)) {
1058
					$name = basename($fileInfo['file']);
1059
				}
1060
			}
1061
			if (!isset($fileInfo['mimetype'])) {
1062
				$fileInfo['mimetype'] = 'application/octet-stream';
1063
			}
1064
			$attach[$name] = $fileInfo;
1065
		}
1066
		$this->_attachments = $attach;
1067
		return $this;
1068
	}
1069
 
1070
/**
1071
 * Add attachments
1072
 *
1073
 * @param string|array $attachments String with the filename or array with filenames
1074
 * @return $this
1075
 * @throws SocketException
1076
 * @see CakeEmail::attachments()
1077
 */
1078
	public function addAttachments($attachments) {
1079
		$current = $this->_attachments;
1080
		$this->attachments($attachments);
1081
		$this->_attachments = array_merge($current, $this->_attachments);
1082
		return $this;
1083
	}
1084
 
1085
/**
1086
 * Get generated message (used by transport classes)
1087
 *
1088
 * @param string $type Use MESSAGE_* constants or null to return the full message as array
1089
 * @return string|array String if have type, array if type is null
1090
 */
1091
	public function message($type = null) {
1092
		switch ($type) {
1093
			case self::MESSAGE_HTML:
1094
				return $this->_htmlMessage;
1095
			case self::MESSAGE_TEXT:
1096
				return $this->_textMessage;
1097
		}
1098
		return $this->_message;
1099
	}
1100
 
1101
/**
1102
 * Configuration to use when send email
1103
 *
1104
 * ### Usage
1105
 *
1106
 * Load configuration from `app/Config/email.php`:
1107
 *
1108
 * `$email->config('default');`
1109
 *
1110
 * Merge an array of configuration into the instance:
1111
 *
1112
 * `$email->config(array('to' => 'bill@example.com'));`
1113
 *
1114
 * @param string|array $config String with configuration name (from email.php), array with config or null to return current config
1115
 * @return string|array|$this
1116
 */
1117
	public function config($config = null) {
1118
		if ($config === null) {
1119
			return $this->_config;
1120
		}
1121
		if (!is_array($config)) {
1122
			$config = (string)$config;
1123
		}
1124
 
1125
		$this->_applyConfig($config);
1126
		return $this;
1127
	}
1128
 
1129
/**
1130
 * Send an email using the specified content, template and layout
1131
 *
1132
 * @param string|array $content String with message or array with messages
1133
 * @return array
1134
 * @throws SocketException
1135
 */
1136
	public function send($content = null) {
1137
		if (empty($this->_from)) {
1138
			throw new SocketException(__d('cake_dev', 'From is not specified.'));
1139
		}
1140
		if (empty($this->_to) && empty($this->_cc) && empty($this->_bcc)) {
1141
			throw new SocketException(__d('cake_dev', 'You need to specify at least one destination for to, cc or bcc.'));
1142
		}
1143
 
1144
		if (is_array($content)) {
1145
			$content = implode("\n", $content) . "\n";
1146
		}
1147
 
1148
		$this->_message = $this->_render($this->_wrap($content));
1149
 
1150
		$contents = $this->transportClass()->send($this);
1151
		if (!empty($this->_config['log'])) {
1152
			$config = array(
1153
				'level' => LOG_DEBUG,
1154
				'scope' => 'email'
1155
			);
1156
			if ($this->_config['log'] !== true) {
1157
				if (!is_array($this->_config['log'])) {
1158
					$this->_config['log'] = array('level' => $this->_config['log']);
1159
				}
1160
				$config = $this->_config['log'] + $config;
1161
			}
1162
			CakeLog::write(
1163
				$config['level'],
1164
				PHP_EOL . $contents['headers'] . PHP_EOL . $contents['message'],
1165
				$config['scope']
1166
			);
1167
		}
1168
		return $contents;
1169
	}
1170
 
1171
/**
1172
 * Static method to fast create an instance of CakeEmail
1173
 *
1174
 * @param string|array $to Address to send (see CakeEmail::to()). If null, will try to use 'to' from transport config
1175
 * @param string $subject String of subject or null to use 'subject' from transport config
1176
 * @param string|array $message String with message or array with variables to be used in render
1177
 * @param string|array $transportConfig String to use config from EmailConfig or array with configs
1178
 * @param bool $send Send the email or just return the instance pre-configured
1179
 * @return CakeEmail Instance of CakeEmail
1180
 * @throws SocketException
1181
 */
1182
	public static function deliver($to = null, $subject = null, $message = null, $transportConfig = 'fast', $send = true) {
1183
		$class = __CLASS__;
1184
		$instance = new $class($transportConfig);
1185
		if ($to !== null) {
1186
			$instance->to($to);
1187
		}
1188
		if ($subject !== null) {
1189
			$instance->subject($subject);
1190
		}
1191
		if (is_array($message)) {
1192
			$instance->viewVars($message);
1193
			$message = null;
1194
		} elseif ($message === null && array_key_exists('message', $config = $instance->config())) {
1195
			$message = $config['message'];
1196
		}
1197
 
1198
		if ($send === true) {
1199
			$instance->send($message);
1200
		}
1201
 
1202
		return $instance;
1203
	}
1204
 
1205
/**
1206
 * Apply the config to an instance
1207
 *
1208
 * @param array $config Configuration options.
1209
 * @return void
1210
 * @throws ConfigureException When configuration file cannot be found, or is missing
1211
 *   the named config.
1212
 */
1213
	protected function _applyConfig($config) {
1214
		if (is_string($config)) {
1215
			if (!class_exists($this->_configClass) && !config('email')) {
1216
				throw new ConfigureException(__d('cake_dev', '%s not found.', APP . 'Config' . DS . 'email.php'));
1217
			}
1218
			$configs = new $this->_configClass();
1219
			if (!isset($configs->{$config})) {
1220
				throw new ConfigureException(__d('cake_dev', 'Unknown email configuration "%s".', $config));
1221
			}
1222
			$config = $configs->{$config};
1223
		}
1224
		$this->_config = $config + $this->_config;
1225
		if (!empty($config['charset'])) {
1226
			$this->charset = $config['charset'];
1227
		}
1228
		if (!empty($config['headerCharset'])) {
1229
			$this->headerCharset = $config['headerCharset'];
1230
		}
1231
		if (empty($this->headerCharset)) {
1232
			$this->headerCharset = $this->charset;
1233
		}
1234
		$simpleMethods = array(
1235
			'from', 'sender', 'to', 'replyTo', 'readReceipt', 'returnPath', 'cc', 'bcc',
1236
			'messageId', 'domain', 'subject', 'viewRender', 'viewVars', 'attachments',
1237
			'transport', 'emailFormat', 'theme', 'helpers', 'emailPattern'
1238
		);
1239
		foreach ($simpleMethods as $method) {
1240
			if (isset($config[$method])) {
1241
				$this->$method($config[$method]);
1242
				unset($config[$method]);
1243
			}
1244
		}
1245
		if (isset($config['headers'])) {
1246
			$this->setHeaders($config['headers']);
1247
			unset($config['headers']);
1248
		}
1249
 
1250
		if (array_key_exists('template', $config)) {
1251
			$this->_template = $config['template'];
1252
		}
1253
		if (array_key_exists('layout', $config)) {
1254
			$this->_layout = $config['layout'];
1255
		}
1256
 
1257
		$this->transportClass()->config($config);
1258
	}
1259
 
1260
/**
1261
 * Reset all CakeEmail internal variables to be able to send out a new email.
1262
 *
1263
 * @return $this
1264
 */
1265
	public function reset() {
1266
		$this->_to = array();
1267
		$this->_from = array();
1268
		$this->_sender = array();
1269
		$this->_replyTo = array();
1270
		$this->_readReceipt = array();
1271
		$this->_returnPath = array();
1272
		$this->_cc = array();
1273
		$this->_bcc = array();
1274
		$this->_messageId = true;
1275
		$this->_subject = '';
1276
		$this->_headers = array();
1277
		$this->_layout = 'default';
1278
		$this->_template = '';
1279
		$this->_viewRender = 'View';
1280
		$this->_viewVars = array();
1281
		$this->_theme = null;
1282
		$this->_helpers = array('Html');
1283
		$this->_textMessage = '';
1284
		$this->_htmlMessage = '';
1285
		$this->_message = '';
1286
		$this->_emailFormat = 'text';
1287
		$this->_transportName = 'Mail';
1288
		$this->_transportClass = null;
1289
		$this->charset = 'utf-8';
1290
		$this->headerCharset = null;
1291
		$this->_attachments = array();
1292
		$this->_config = array();
1293
		$this->_emailPattern = null;
1294
		return $this;
1295
	}
1296
 
1297
/**
1298
 * Encode the specified string using the current charset
1299
 *
1300
 * @param string $text String to encode
1301
 * @return string Encoded string
1302
 */
1303
	protected function _encode($text) {
1304
		$internalEncoding = function_exists('mb_internal_encoding');
1305
		if ($internalEncoding) {
1306
			$restore = mb_internal_encoding();
1307
			mb_internal_encoding($this->_appCharset);
1308
		}
1309
		if (empty($this->headerCharset)) {
1310
			$this->headerCharset = $this->charset;
1311
		}
1312
		$return = mb_encode_mimeheader($text, $this->headerCharset, 'B');
1313
		if ($internalEncoding) {
1314
			mb_internal_encoding($restore);
1315
		}
1316
		return $return;
1317
	}
1318
 
1319
/**
1320
 * Translates a string for one charset to another if the App.encoding value
1321
 * differs and the mb_convert_encoding function exists
1322
 *
1323
 * @param string $text The text to be converted
1324
 * @param string $charset the target encoding
1325
 * @return string
1326
 */
1327
	protected function _encodeString($text, $charset) {
1328
		if ($this->_appCharset === $charset || !function_exists('mb_convert_encoding')) {
1329
			return $text;
1330
		}
1331
		return mb_convert_encoding($text, $charset, $this->_appCharset);
1332
	}
1333
 
1334
/**
1335
 * Wrap the message to follow the RFC 2822 - 2.1.1
1336
 *
1337
 * @param string $message Message to wrap
1338
 * @param int $wrapLength The line length
1339
 * @return array Wrapped message
1340
 */
1341
	protected function _wrap($message, $wrapLength = CakeEmail::LINE_LENGTH_MUST) {
1342
		if (strlen($message) === 0) {
1343
			return array('');
1344
		}
1345
		$message = str_replace(array("\r\n", "\r"), "\n", $message);
1346
		$lines = explode("\n", $message);
1347
		$formatted = array();
1348
		$cut = ($wrapLength == CakeEmail::LINE_LENGTH_MUST);
1349
 
1350
		foreach ($lines as $line) {
1351
			if (empty($line)) {
1352
				$formatted[] = '';
1353
				continue;
1354
			}
1355
			if (strlen($line) < $wrapLength) {
1356
				$formatted[] = $line;
1357
				continue;
1358
			}
1359
			if (!preg_match('/<[a-z]+.*>/i', $line)) {
1360
				$formatted = array_merge(
1361
					$formatted,
1362
					explode("\n", wordwrap($line, $wrapLength, "\n", $cut))
1363
				);
1364
				continue;
1365
			}
1366
 
1367
			$tagOpen = false;
1368
			$tmpLine = $tag = '';
1369
			$tmpLineLength = 0;
1370
			for ($i = 0, $count = strlen($line); $i < $count; $i++) {
1371
				$char = $line[$i];
1372
				if ($tagOpen) {
1373
					$tag .= $char;
1374
					if ($char === '>') {
1375
						$tagLength = strlen($tag);
1376
						if ($tagLength + $tmpLineLength < $wrapLength) {
1377
							$tmpLine .= $tag;
1378
							$tmpLineLength += $tagLength;
1379
						} else {
1380
							if ($tmpLineLength > 0) {
1381
								$formatted = array_merge(
1382
									$formatted,
1383
									explode("\n", wordwrap(trim($tmpLine), $wrapLength, "\n", $cut))
1384
								);
1385
								$tmpLine = '';
1386
								$tmpLineLength = 0;
1387
							}
1388
							if ($tagLength > $wrapLength) {
1389
								$formatted[] = $tag;
1390
							} else {
1391
								$tmpLine = $tag;
1392
								$tmpLineLength = $tagLength;
1393
							}
1394
						}
1395
						$tag = '';
1396
						$tagOpen = false;
1397
					}
1398
					continue;
1399
				}
1400
				if ($char === '<') {
1401
					$tagOpen = true;
1402
					$tag = '<';
1403
					continue;
1404
				}
1405
				if ($char === ' ' && $tmpLineLength >= $wrapLength) {
1406
					$formatted[] = $tmpLine;
1407
					$tmpLineLength = 0;
1408
					continue;
1409
				}
1410
				$tmpLine .= $char;
1411
				$tmpLineLength++;
1412
				if ($tmpLineLength === $wrapLength) {
1413
					$nextChar = $line[$i + 1];
1414
					if ($nextChar === ' ' || $nextChar === '<') {
1415
						$formatted[] = trim($tmpLine);
1416
						$tmpLine = '';
1417
						$tmpLineLength = 0;
1418
						if ($nextChar === ' ') {
1419
							$i++;
1420
						}
1421
					} else {
1422
						$lastSpace = strrpos($tmpLine, ' ');
1423
						if ($lastSpace === false) {
1424
							continue;
1425
						}
1426
						$formatted[] = trim(substr($tmpLine, 0, $lastSpace));
1427
						$tmpLine = substr($tmpLine, $lastSpace + 1);
1428
 
1429
						$tmpLineLength = strlen($tmpLine);
1430
					}
1431
				}
1432
			}
1433
			if (!empty($tmpLine)) {
1434
				$formatted[] = $tmpLine;
1435
			}
1436
		}
1437
		$formatted[] = '';
1438
		return $formatted;
1439
	}
1440
 
1441
/**
1442
 * Create unique boundary identifier
1443
 *
1444
 * @return void
1445
 */
1446
	protected function _createBoundary() {
1447
		if (!empty($this->_attachments) || $this->_emailFormat === 'both') {
1448
			$this->_boundary = md5(uniqid(time()));
1449
		}
1450
	}
1451
 
1452
/**
1453
 * Attach non-embedded files by adding file contents inside boundaries.
1454
 *
1455
 * @param string $boundary Boundary to use. If null, will default to $this->_boundary
1456
 * @return array An array of lines to add to the message
1457
 */
1458
	protected function _attachFiles($boundary = null) {
1459
		if ($boundary === null) {
1460
			$boundary = $this->_boundary;
1461
		}
1462
 
1463
		$msg = array();
1464
		foreach ($this->_attachments as $filename => $fileInfo) {
1465
			if (!empty($fileInfo['contentId'])) {
1466
				continue;
1467
			}
1468
			$data = isset($fileInfo['data']) ? $fileInfo['data'] : $this->_readFile($fileInfo['file']);
1469
 
1470
			$msg[] = '--' . $boundary;
1471
			$msg[] = 'Content-Type: ' . $fileInfo['mimetype'];
1472
			$msg[] = 'Content-Transfer-Encoding: base64';
1473
			if (
1474
				!isset($fileInfo['contentDisposition']) ||
1475
				$fileInfo['contentDisposition']
1476
			) {
1477
				$msg[] = 'Content-Disposition: attachment; filename="' . $filename . '"';
1478
			}
1479
			$msg[] = '';
1480
			$msg[] = $data;
1481
			$msg[] = '';
1482
		}
1483
		return $msg;
1484
	}
1485
 
1486
/**
1487
 * Read the file contents and return a base64 version of the file contents.
1488
 *
1489
 * @param string $path The absolute path to the file to read.
1490
 * @return string File contents in base64 encoding
1491
 */
1492
	protected function _readFile($path) {
1493
		$File = new File($path);
1494
		return chunk_split(base64_encode($File->read()));
1495
	}
1496
 
1497
/**
1498
 * Attach inline/embedded files to the message.
1499
 *
1500
 * @param string $boundary Boundary to use. If null, will default to $this->_boundary
1501
 * @return array An array of lines to add to the message
1502
 */
1503
	protected function _attachInlineFiles($boundary = null) {
1504
		if ($boundary === null) {
1505
			$boundary = $this->_boundary;
1506
		}
1507
 
1508
		$msg = array();
1509
		foreach ($this->_attachments as $filename => $fileInfo) {
1510
			if (empty($fileInfo['contentId'])) {
1511
				continue;
1512
			}
1513
			$data = isset($fileInfo['data']) ? $fileInfo['data'] : $this->_readFile($fileInfo['file']);
1514
 
1515
			$msg[] = '--' . $boundary;
1516
			$msg[] = 'Content-Type: ' . $fileInfo['mimetype'];
1517
			$msg[] = 'Content-Transfer-Encoding: base64';
1518
			$msg[] = 'Content-ID: <' . $fileInfo['contentId'] . '>';
1519
			$msg[] = 'Content-Disposition: inline; filename="' . $filename . '"';
1520
			$msg[] = '';
1521
			$msg[] = $data;
1522
			$msg[] = '';
1523
		}
1524
		return $msg;
1525
	}
1526
 
1527
/**
1528
 * Render the body of the email.
1529
 *
1530
 * @param array $content Content to render
1531
 * @return array Email body ready to be sent
1532
 */
1533
	protected function _render($content) {
1534
		$this->_textMessage = $this->_htmlMessage = '';
1535
 
1536
		$content = implode("\n", $content);
1537
		$rendered = $this->_renderTemplates($content);
1538
 
1539
		$this->_createBoundary();
1540
		$msg = array();
1541
 
1542
		$contentIds = array_filter((array)Hash::extract($this->_attachments, '{s}.contentId'));
1543
		$hasInlineAttachments = count($contentIds) > 0;
1544
		$hasAttachments = !empty($this->_attachments);
1545
		$hasMultipleTypes = count($rendered) > 1;
1546
		$multiPart = ($hasAttachments || $hasMultipleTypes);
1547
 
1548
		$boundary = $relBoundary = $textBoundary = $this->_boundary;
1549
 
1550
		if ($hasInlineAttachments) {
1551
			$msg[] = '--' . $boundary;
1552
			$msg[] = 'Content-Type: multipart/related; boundary="rel-' . $boundary . '"';
1553
			$msg[] = '';
1554
			$relBoundary = $textBoundary = 'rel-' . $boundary;
1555
		}
1556
 
1557
		if ($hasMultipleTypes && $hasAttachments) {
1558
			$msg[] = '--' . $relBoundary;
1559
			$msg[] = 'Content-Type: multipart/alternative; boundary="alt-' . $boundary . '"';
1560
			$msg[] = '';
1561
			$textBoundary = 'alt-' . $boundary;
1562
		}
1563
 
1564
		if (isset($rendered['text'])) {
1565
			if ($multiPart) {
1566
				$msg[] = '--' . $textBoundary;
1567
				$msg[] = 'Content-Type: text/plain; charset=' . $this->_getContentTypeCharset();
1568
				$msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding();
1569
				$msg[] = '';
1570
			}
1571
			$this->_textMessage = $rendered['text'];
1572
			$content = explode("\n", $this->_textMessage);
1573
			$msg = array_merge($msg, $content);
1574
			$msg[] = '';
1575
		}
1576
 
1577
		if (isset($rendered['html'])) {
1578
			if ($multiPart) {
1579
				$msg[] = '--' . $textBoundary;
1580
				$msg[] = 'Content-Type: text/html; charset=' . $this->_getContentTypeCharset();
1581
				$msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding();
1582
				$msg[] = '';
1583
			}
1584
			$this->_htmlMessage = $rendered['html'];
1585
			$content = explode("\n", $this->_htmlMessage);
1586
			$msg = array_merge($msg, $content);
1587
			$msg[] = '';
1588
		}
1589
 
1590
		if ($textBoundary !== $relBoundary) {
1591
			$msg[] = '--' . $textBoundary . '--';
1592
			$msg[] = '';
1593
		}
1594
 
1595
		if ($hasInlineAttachments) {
1596
			$attachments = $this->_attachInlineFiles($relBoundary);
1597
			$msg = array_merge($msg, $attachments);
1598
			$msg[] = '';
1599
			$msg[] = '--' . $relBoundary . '--';
1600
			$msg[] = '';
1601
		}
1602
 
1603
		if ($hasAttachments) {
1604
			$attachments = $this->_attachFiles($boundary);
1605
			$msg = array_merge($msg, $attachments);
1606
		}
1607
		if ($hasAttachments || $hasMultipleTypes) {
1608
			$msg[] = '';
1609
			$msg[] = '--' . $boundary . '--';
1610
			$msg[] = '';
1611
		}
1612
		return $msg;
1613
	}
1614
 
1615
/**
1616
 * Gets the text body types that are in this email message
1617
 *
1618
 * @return array Array of types. Valid types are 'text' and 'html'
1619
 */
1620
	protected function _getTypes() {
1621
		$types = array($this->_emailFormat);
1622
		if ($this->_emailFormat === 'both') {
1623
			$types = array('html', 'text');
1624
		}
1625
		return $types;
1626
	}
1627
 
1628
/**
1629
 * Build and set all the view properties needed to render the templated emails.
1630
 * If there is no template set, the $content will be returned in a hash
1631
 * of the text content types for the email.
1632
 *
1633
 * @param string $content The content passed in from send() in most cases.
1634
 * @return array The rendered content with html and text keys.
1635
 */
1636
	protected function _renderTemplates($content) {
1637
		$types = $this->_getTypes();
1638
		$rendered = array();
1639
		if (empty($this->_template)) {
1640
			foreach ($types as $type) {
1641
				$rendered[$type] = $this->_encodeString($content, $this->charset);
1642
			}
1643
			return $rendered;
1644
		}
1645
		$viewClass = $this->_viewRender;
1646
		if ($viewClass !== 'View') {
1647
			list($plugin, $viewClass) = pluginSplit($viewClass, true);
1648
			$viewClass .= 'View';
1649
			App::uses($viewClass, $plugin . 'View');
1650
		}
1651
 
1652
		$View = new $viewClass(null);
1653
		$View->viewVars = $this->_viewVars;
1654
		$View->helpers = $this->_helpers;
1655
 
1656
		if ($this->_theme) {
1657
			$View->theme = $this->_theme;
1658
		}
1659
 
1660
		$View->loadHelpers();
1661
 
1662
		list($templatePlugin, $template) = pluginSplit($this->_template);
1663
		list($layoutPlugin, $layout) = pluginSplit($this->_layout);
1664
		if ($templatePlugin) {
1665
			$View->plugin = $templatePlugin;
1666
		} elseif ($layoutPlugin) {
1667
			$View->plugin = $layoutPlugin;
1668
		}
1669
 
1670
		if ($View->get('content') === null) {
1671
			$View->set('content', $content);
1672
		}
1673
 
1674
		// Convert null to false, as View needs false to disable
1675
		// the layout.
1676
		if ($this->_layout === null) {
1677
			$this->_layout = false;
1678
		}
1679
 
1680
		foreach ($types as $type) {
1681
			$View->hasRendered = false;
1682
			$View->viewPath = $View->layoutPath = 'Emails' . DS . $type;
1683
 
1684
			$render = $View->render($this->_template, $this->_layout);
1685
			$render = str_replace(array("\r\n", "\r"), "\n", $render);
1686
			$rendered[$type] = $this->_encodeString($render, $this->charset);
1687
		}
1688
 
1689
		foreach ($rendered as $type => $content) {
1690
			$rendered[$type] = $this->_wrap($content);
1691
			$rendered[$type] = implode("\n", $rendered[$type]);
1692
			$rendered[$type] = rtrim($rendered[$type], "\n");
1693
		}
1694
		return $rendered;
1695
	}
1696
 
1697
/**
1698
 * Return the Content-Transfer Encoding value based on the set charset
1699
 *
1700
 * @return string
1701
 */
1702
	protected function _getContentTransferEncoding() {
1703
		$charset = strtoupper($this->charset);
1704
		if (in_array($charset, $this->_charset8bit)) {
1705
			return '8bit';
1706
		}
1707
		return '7bit';
1708
	}
1709
 
1710
/**
1711
 * Return charset value for Content-Type.
1712
 *
1713
 * Checks fallback/compatibility types which include workarounds
1714
 * for legacy japanese character sets.
1715
 *
1716
 * @return string
1717
 */
1718
	protected function _getContentTypeCharset() {
1719
		$charset = strtoupper($this->charset);
1720
		if (array_key_exists($charset, $this->_contentTypeCharset)) {
1721
			return strtoupper($this->_contentTypeCharset[$charset]);
1722
		}
1723
		return strtoupper($this->charset);
1724
	}
1725
 
1726
}