Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

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