Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

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