PageRenderTime 57ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/Cake/Network/Email/CakeEmail.php

https://bitbucket.org/udeshika/fake_twitter
PHP | 1460 lines | 777 code | 115 blank | 568 comment | 137 complexity | 9f49d4d1ca5f1b85da7d86095079aac9 MD5 | raw file
  1. <?php
  2. /**
  3. * Cake E-Mail
  4. *
  5. * PHP 5
  6. *
  7. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8. * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * Redistributions of files must retain the above copyright notice.
  12. *
  13. * @copyright Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14. * @link http://cakephp.org CakePHP(tm) Project
  15. * @package Cake.Network.Email
  16. * @since CakePHP(tm) v 2.0.0
  17. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  18. */
  19. App::uses('Validation', 'Utility');
  20. App::uses('Multibyte', 'I18n');
  21. App::uses('AbstractTransport', 'Network/Email');
  22. App::uses('String', 'Utility');
  23. App::uses('View', 'View');
  24. App::import('I18n', 'Multibyte');
  25. /**
  26. * Cake e-mail class.
  27. *
  28. * This class is used for handling Internet Message Format based
  29. * based on the standard outlined in http://www.rfc-editor.org/rfc/rfc2822.txt
  30. *
  31. * @package Cake.Network.Email
  32. */
  33. class CakeEmail {
  34. /**
  35. * Default X-Mailer
  36. *
  37. * @constant EMAIL_CLIENT
  38. */
  39. const EMAIL_CLIENT = 'CakePHP Email';
  40. /**
  41. * Line length - no should more - RFC 2822 - 2.1.1
  42. *
  43. * @constant LINE_LENGTH_SHOULD
  44. */
  45. const LINE_LENGTH_SHOULD = 78;
  46. /**
  47. * Line length - no must more - RFC 2822 - 2.1.1
  48. *
  49. * @constant LINE_LENGTH_MUST
  50. */
  51. const LINE_LENGTH_MUST = 998;
  52. /**
  53. * Type of message - HTML
  54. *
  55. * @constant MESSAGE_HTML
  56. */
  57. const MESSAGE_HTML = 'html';
  58. /**
  59. * Type of message - TEXT
  60. *
  61. * @constant MESSAGE_TEXT
  62. */
  63. const MESSAGE_TEXT = 'text';
  64. /**
  65. * Recipient of the email
  66. *
  67. * @var array
  68. */
  69. protected $_to = array();
  70. /**
  71. * The mail which the email is sent from
  72. *
  73. * @var array
  74. */
  75. protected $_from = array();
  76. /**
  77. * The sender email
  78. *
  79. * @var array();
  80. */
  81. protected $_sender = array();
  82. /**
  83. * The email the recipient will reply to
  84. *
  85. * @var array
  86. */
  87. protected $_replyTo = array();
  88. /**
  89. * The read receipt email
  90. *
  91. * @var array
  92. */
  93. protected $_readReceipt = array();
  94. /**
  95. * The mail that will be used in case of any errors like
  96. * - Remote mailserver down
  97. * - Remote user has exceeded his quota
  98. * - Unknown user
  99. *
  100. * @var array
  101. */
  102. protected $_returnPath = array();
  103. /**
  104. * Carbon Copy
  105. *
  106. * List of email's that should receive a copy of the email.
  107. * The Recipient WILL be able to see this list
  108. *
  109. * @var array
  110. */
  111. protected $_cc = array();
  112. /**
  113. * Blind Carbon Copy
  114. *
  115. * List of email's that should receive a copy of the email.
  116. * The Recipient WILL NOT be able to see this list
  117. *
  118. * @var array
  119. */
  120. protected $_bcc = array();
  121. /**
  122. * Message ID
  123. *
  124. * @var mixed True to generate, False to ignore, String with value
  125. */
  126. protected $_messageId = true;
  127. /**
  128. * The subject of the email
  129. *
  130. * @var string
  131. */
  132. protected $_subject = '';
  133. /**
  134. * Associative array of a user defined headers
  135. * Keys will be prefixed 'X-' as per RFC2822 Section 4.7.5
  136. *
  137. * @var array
  138. */
  139. protected $_headers = array();
  140. /**
  141. * Layout for the View
  142. *
  143. * @var string
  144. */
  145. protected $_layout = 'default';
  146. /**
  147. * Template for the view
  148. *
  149. * @var string
  150. */
  151. protected $_template = '';
  152. /**
  153. * View for render
  154. *
  155. * @var string
  156. */
  157. protected $_viewRender = 'View';
  158. /**
  159. * Vars to sent to render
  160. *
  161. * @var array
  162. */
  163. protected $_viewVars = array();
  164. /**
  165. * Helpers to be used in the render
  166. *
  167. * @var array
  168. */
  169. protected $_helpers = array('Html');
  170. /**
  171. * Text message
  172. *
  173. * @var string
  174. */
  175. protected $_textMessage = '';
  176. /**
  177. * Html message
  178. *
  179. * @var string
  180. */
  181. protected $_htmlMessage = '';
  182. /**
  183. * Final message to send
  184. *
  185. * @var array
  186. */
  187. protected $_message = array();
  188. /**
  189. * Available formats to be sent.
  190. *
  191. * @var array
  192. */
  193. protected $_emailFormatAvailable = array('text', 'html', 'both');
  194. /**
  195. * What format should the email be sent in
  196. *
  197. * @var string
  198. */
  199. protected $_emailFormat = 'text';
  200. /**
  201. * What method should the email be sent
  202. *
  203. * @var string
  204. */
  205. protected $_transportName = 'Mail';
  206. /**
  207. * Instance of transport class
  208. *
  209. * @var AbstractTransport
  210. */
  211. protected $_transportClass = null;
  212. /**
  213. * Charset the email body is sent in
  214. *
  215. *
  216. * @var string
  217. */
  218. public $charset = 'utf-8';
  219. /**
  220. * Charset the email header is sent in
  221. * If null, the $charset property will be used as default
  222. * @var string
  223. */
  224. public $headerCharset = null;
  225. /**
  226. * The application wide charset, used to encode headers and body
  227. * @var string
  228. */
  229. public $_appCharset = null;
  230. /**
  231. * List of files that should be attached to the email.
  232. *
  233. * Only absolute paths
  234. *
  235. * @var array
  236. */
  237. protected $_attachments = array();
  238. /**
  239. * If set, boundary to use for multipart mime messages
  240. *
  241. * @var string
  242. */
  243. protected $_boundary = null;
  244. /**
  245. * Configuration to transport
  246. *
  247. * @var mixed
  248. */
  249. protected $_config = array();
  250. /**
  251. * 8Bit character sets
  252. *
  253. * @var array
  254. */
  255. protected $_charset8bit = array('UTF-8', 'SHIFT_JIS');
  256. /**
  257. * Constructor
  258. * @param mixed $config Array of configs, or string to load configs from email.php
  259. *
  260. */
  261. public function __construct($config = null) {
  262. $this->_appCharset = Configure::read('App.encoding');
  263. if ($this->_appCharset !== null) {
  264. $this->charset = $this->_appCharset;
  265. }
  266. if ($config) {
  267. $this->config($config);
  268. }
  269. if (empty($this->headerCharset)) {
  270. $this->headerCharset = $this->charset;
  271. }
  272. }
  273. /**
  274. * From
  275. *
  276. * @param mixed $email
  277. * @param string $name
  278. * @return mixed
  279. * @throws SocketException
  280. */
  281. public function from($email = null, $name = null) {
  282. if ($email === null) {
  283. return $this->_from;
  284. }
  285. return $this->_setEmailSingle('_from', $email, $name, __d('cake_dev', 'From requires only 1 email address.'));
  286. }
  287. /**
  288. * Sender
  289. *
  290. * @param mixed $email
  291. * @param string $name
  292. * @return mixed
  293. * @throws SocketException
  294. */
  295. public function sender($email = null, $name = null) {
  296. if ($email === null) {
  297. return $this->_sender;
  298. }
  299. return $this->_setEmailSingle('_sender', $email, $name, __d('cake_dev', 'Sender requires only 1 email address.'));
  300. }
  301. /**
  302. * Reply-To
  303. *
  304. * @param mixed $email
  305. * @param string $name
  306. * @return mixed
  307. * @throws SocketException
  308. */
  309. public function replyTo($email = null, $name = null) {
  310. if ($email === null) {
  311. return $this->_replyTo;
  312. }
  313. return $this->_setEmailSingle('_replyTo', $email, $name, __d('cake_dev', 'Reply-To requires only 1 email address.'));
  314. }
  315. /**
  316. * Read Receipt (Disposition-Notification-To header)
  317. *
  318. * @param mixed $email
  319. * @param string $name
  320. * @return mixed
  321. * @throws SocketException
  322. */
  323. public function readReceipt($email = null, $name = null) {
  324. if ($email === null) {
  325. return $this->_readReceipt;
  326. }
  327. return $this->_setEmailSingle('_readReceipt', $email, $name, __d('cake_dev', 'Disposition-Notification-To requires only 1 email address.'));
  328. }
  329. /**
  330. * Return Path
  331. *
  332. * @param mixed $email
  333. * @param string $name
  334. * @return mixed
  335. * @throws SocketException
  336. */
  337. public function returnPath($email = null, $name = null) {
  338. if ($email === null) {
  339. return $this->_returnPath;
  340. }
  341. return $this->_setEmailSingle('_returnPath', $email, $name, __d('cake_dev', 'Return-Path requires only 1 email address.'));
  342. }
  343. /**
  344. * To
  345. *
  346. * @param mixed $email Null to get, String with email, Array with email as key, name as value or email as value (without name)
  347. * @param string $name
  348. * @return mixed
  349. */
  350. public function to($email = null, $name = null) {
  351. if ($email === null) {
  352. return $this->_to;
  353. }
  354. return $this->_setEmail('_to', $email, $name);
  355. }
  356. /**
  357. * Add To
  358. *
  359. * @param mixed $email String with email, Array with email as key, name as value or email as value (without name)
  360. * @param string $name
  361. * @return CakeEmail $this
  362. */
  363. public function addTo($email, $name = null) {
  364. return $this->_addEmail('_to', $email, $name);
  365. }
  366. /**
  367. * Cc
  368. *
  369. * @param mixed $email String with email, Array with email as key, name as value or email as value (without name)
  370. * @param string $name
  371. * @return mixed
  372. */
  373. public function cc($email = null, $name = null) {
  374. if ($email === null) {
  375. return $this->_cc;
  376. }
  377. return $this->_setEmail('_cc', $email, $name);
  378. }
  379. /**
  380. * Add Cc
  381. *
  382. * @param mixed $email String with email, Array with email as key, name as value or email as value (without name)
  383. * @param string $name
  384. * @return CakeEmail $this
  385. */
  386. public function addCc($email, $name = null) {
  387. return $this->_addEmail('_cc', $email, $name);
  388. }
  389. /**
  390. * Bcc
  391. *
  392. * @param mixed $email String with email, Array with email as key, name as value or email as value (without name)
  393. * @param string $name
  394. * @return mixed
  395. */
  396. public function bcc($email = null, $name = null) {
  397. if ($email === null) {
  398. return $this->_bcc;
  399. }
  400. return $this->_setEmail('_bcc', $email, $name);
  401. }
  402. /**
  403. * Add Bcc
  404. *
  405. * @param mixed $email String with email, Array with email as key, name as value or email as value (without name)
  406. * @param string $name
  407. * @return CakeEmail $this
  408. */
  409. public function addBcc($email, $name = null) {
  410. return $this->_addEmail('_bcc', $email, $name);
  411. }
  412. /**
  413. * Set email
  414. *
  415. * @param string $varName
  416. * @param mixed $email
  417. * @param mixed $name
  418. * @return CakeEmail $this
  419. * @throws SocketException
  420. */
  421. protected function _setEmail($varName, $email, $name) {
  422. if (!is_array($email)) {
  423. if (!Validation::email($email)) {
  424. throw new SocketException(__d('cake_dev', 'Invalid email: "%s"', $email));
  425. }
  426. if ($name === null) {
  427. $name = $email;
  428. }
  429. $this->{$varName} = array($email => $name);
  430. return $this;
  431. }
  432. $list = array();
  433. foreach ($email as $key => $value) {
  434. if (is_int($key)) {
  435. $key = $value;
  436. }
  437. if (!Validation::email($key)) {
  438. throw new SocketException(__d('cake_dev', 'Invalid email: "%s"', $key));
  439. }
  440. $list[$key] = $value;
  441. }
  442. $this->{$varName} = $list;
  443. return $this;
  444. }
  445. /**
  446. * Set only 1 email
  447. *
  448. * @param string $varName
  449. * @param mixed $email
  450. * @param string $name
  451. * @param string $throwMessage
  452. * @return CakeEmail $this
  453. * @throws SocketException
  454. */
  455. protected function _setEmailSingle($varName, $email, $name, $throwMessage) {
  456. $current = $this->{$varName};
  457. $this->_setEmail($varName, $email, $name);
  458. if (count($this->{$varName}) !== 1) {
  459. $this->{$varName} = $current;
  460. throw new SocketException($throwMessage);
  461. }
  462. return $this;
  463. }
  464. /**
  465. * Add email
  466. *
  467. * @param string $varName
  468. * @param mixed $email
  469. * @param mixed $name
  470. * @return CakeEmail $this
  471. * @throws SocketException
  472. */
  473. protected function _addEmail($varName, $email, $name) {
  474. if (!is_array($email)) {
  475. if (!Validation::email($email)) {
  476. throw new SocketException(__d('cake_dev', 'Invalid email: "%s"', $email));
  477. }
  478. if ($name === null) {
  479. $name = $email;
  480. }
  481. $this->{$varName}[$email] = $name;
  482. return $this;
  483. }
  484. $list = array();
  485. foreach ($email as $key => $value) {
  486. if (is_int($key)) {
  487. $key = $value;
  488. }
  489. if (!Validation::email($key)) {
  490. throw new SocketException(__d('cake_dev', 'Invalid email: "%s"', $key));
  491. }
  492. $list[$key] = $value;
  493. }
  494. $this->{$varName} = array_merge($this->{$varName}, $list);
  495. return $this;
  496. }
  497. /**
  498. * Get/Set Subject.
  499. *
  500. * @param null|string $subject
  501. * @return mixed
  502. */
  503. public function subject($subject = null) {
  504. if ($subject === null) {
  505. return $this->_subject;
  506. }
  507. $this->_subject = $this->_encode((string)$subject);
  508. return $this;
  509. }
  510. /**
  511. * Sets headers for the message
  512. *
  513. * @param array $headers Associative array containing headers to be set.
  514. * @return CakeEmail $this
  515. * @throws SocketException
  516. */
  517. public function setHeaders($headers) {
  518. if (!is_array($headers)) {
  519. throw new SocketException(__d('cake_dev', '$headers should be an array.'));
  520. }
  521. $this->_headers = $headers;
  522. return $this;
  523. }
  524. /**
  525. * Add header for the message
  526. *
  527. * @param array $headers
  528. * @return object $this
  529. * @throws SocketException
  530. */
  531. public function addHeaders($headers) {
  532. if (!is_array($headers)) {
  533. throw new SocketException(__d('cake_dev', '$headers should be an array.'));
  534. }
  535. $this->_headers = array_merge($this->_headers, $headers);
  536. return $this;
  537. }
  538. /**
  539. * Get list of headers
  540. *
  541. * ### Includes:
  542. *
  543. * - `from`
  544. * - `replyTo`
  545. * - `readReceipt`
  546. * - `returnPath`
  547. * - `to`
  548. * - `cc`
  549. * - `bcc`
  550. * - `subject`
  551. *
  552. * @param array $include
  553. * @return array
  554. */
  555. public function getHeaders($include = array()) {
  556. if ($include == array_values($include)) {
  557. $include = array_fill_keys($include, true);
  558. }
  559. $defaults = array_fill_keys(array('from', 'sender', 'replyTo', 'readReceipt', 'returnPath', 'to', 'cc', 'bcc', 'subject'), false);
  560. $include += $defaults;
  561. $headers = array();
  562. $relation = array(
  563. 'from' => 'From',
  564. 'replyTo' => 'Reply-To',
  565. 'readReceipt' => 'Disposition-Notification-To',
  566. 'returnPath' => 'Return-Path'
  567. );
  568. foreach ($relation as $var => $header) {
  569. if ($include[$var]) {
  570. $var = '_' . $var;
  571. $headers[$header] = current($this->_formatAddress($this->{$var}));
  572. }
  573. }
  574. if ($include['sender']) {
  575. if (key($this->_sender) === key($this->_from)) {
  576. $headers['Sender'] = '';
  577. } else {
  578. $headers['Sender'] = current($this->_formatAddress($this->_sender));
  579. }
  580. }
  581. foreach (array('to', 'cc', 'bcc') as $var) {
  582. if ($include[$var]) {
  583. $classVar = '_' . $var;
  584. $headers[ucfirst($var)] = implode(', ', $this->_formatAddress($this->{$classVar}));
  585. }
  586. }
  587. $headers += $this->_headers;
  588. if (!isset($headers['X-Mailer'])) {
  589. $headers['X-Mailer'] = self::EMAIL_CLIENT;
  590. }
  591. if (!isset($headers['Date'])) {
  592. $headers['Date'] = date(DATE_RFC2822);
  593. }
  594. if ($this->_messageId !== false) {
  595. if ($this->_messageId === true) {
  596. $headers['Message-ID'] = '<' . str_replace('-', '', String::UUID()) . '@' . env('HTTP_HOST') . '>';
  597. } else {
  598. $headers['Message-ID'] = $this->_messageId;
  599. }
  600. }
  601. if ($include['subject']) {
  602. $headers['Subject'] = $this->_subject;
  603. }
  604. $headers['MIME-Version'] = '1.0';
  605. if (!empty($this->_attachments) || $this->_emailFormat === 'both') {
  606. $headers['Content-Type'] = 'multipart/mixed; boundary="' . $this->_boundary . '"';
  607. } elseif ($this->_emailFormat === 'text') {
  608. $headers['Content-Type'] = 'text/plain; charset=' . $this->charset;
  609. } elseif ($this->_emailFormat === 'html') {
  610. $headers['Content-Type'] = 'text/html; charset=' . $this->charset;
  611. }
  612. $headers['Content-Transfer-Encoding'] = $this->_getContentTransferEncoding();
  613. return $headers;
  614. }
  615. /**
  616. * Format addresses
  617. *
  618. * @param array $address
  619. * @return array
  620. */
  621. protected function _formatAddress($address) {
  622. $return = array();
  623. foreach ($address as $email => $alias) {
  624. if ($email === $alias) {
  625. $return[] = $email;
  626. } else {
  627. $return[] = sprintf('%s <%s>', $this->_encode($alias), $email);
  628. }
  629. }
  630. return $return;
  631. }
  632. /**
  633. * Template and layout
  634. *
  635. * @param mixed $template Template name or null to not use
  636. * @param mixed $layout Layout name or null to not use
  637. * @return mixed
  638. */
  639. public function template($template = false, $layout = false) {
  640. if ($template === false) {
  641. return array(
  642. 'template' => $this->_template,
  643. 'layout' => $this->_layout
  644. );
  645. }
  646. $this->_template = $template;
  647. if ($layout !== false) {
  648. $this->_layout = $layout;
  649. }
  650. return $this;
  651. }
  652. /**
  653. * View class for render
  654. *
  655. * @param string $viewClass
  656. * @return mixed
  657. */
  658. public function viewRender($viewClass = null) {
  659. if ($viewClass === null) {
  660. return $this->_viewRender;
  661. }
  662. $this->_viewRender = $viewClass;
  663. return $this;
  664. }
  665. /**
  666. * Variables to be set on render
  667. *
  668. * @param array $viewVars
  669. * @return mixed
  670. */
  671. public function viewVars($viewVars = null) {
  672. if ($viewVars === null) {
  673. return $this->_viewVars;
  674. }
  675. $this->_viewVars = array_merge($this->_viewVars, (array)$viewVars);
  676. return $this;
  677. }
  678. /**
  679. * Helpers to be used in render
  680. *
  681. * @param array $helpers
  682. * @return mixed
  683. */
  684. public function helpers($helpers = null) {
  685. if ($helpers === null) {
  686. return $this->_helpers;
  687. }
  688. $this->_helpers = (array)$helpers;
  689. return $this;
  690. }
  691. /**
  692. * Email format
  693. *
  694. * @param string $format
  695. * @return mixed
  696. * @throws SocketException
  697. */
  698. public function emailFormat($format = null) {
  699. if ($format === null) {
  700. return $this->_emailFormat;
  701. }
  702. if (!in_array($format, $this->_emailFormatAvailable)) {
  703. throw new SocketException(__d('cake_dev', 'Format not available.'));
  704. }
  705. $this->_emailFormat = $format;
  706. return $this;
  707. }
  708. /**
  709. * Transport name
  710. *
  711. * @param string $name
  712. * @return mixed
  713. */
  714. public function transport($name = null) {
  715. if ($name === null) {
  716. return $this->_transportName;
  717. }
  718. $this->_transportName = (string)$name;
  719. $this->_transportClass = null;
  720. return $this;
  721. }
  722. /**
  723. * Return the transport class
  724. *
  725. * @return CakeEmail
  726. * @throws SocketException
  727. */
  728. public function transportClass() {
  729. if ($this->_transportClass) {
  730. return $this->_transportClass;
  731. }
  732. list($plugin, $transportClassname) = pluginSplit($this->_transportName, true);
  733. $transportClassname .= 'Transport';
  734. App::uses($transportClassname, $plugin . 'Network/Email');
  735. if (!class_exists($transportClassname)) {
  736. throw new SocketException(__d('cake_dev', 'Class "%s" not found.', $transportClassname));
  737. } elseif (!method_exists($transportClassname, 'send')) {
  738. throw new SocketException(__d('cake_dev', 'The "%s" do not have send method.', $transportClassname));
  739. }
  740. return $this->_transportClass = new $transportClassname();
  741. }
  742. /**
  743. * Message-ID
  744. *
  745. * @param mixed $message True to generate a new Message-ID, False to ignore (not send in email), String to set as Message-ID
  746. * @return mixed
  747. * @throws SocketException
  748. */
  749. public function messageId($message = null) {
  750. if ($message === null) {
  751. return $this->_messageId;
  752. }
  753. if (is_bool($message)) {
  754. $this->_messageId = $message;
  755. } else {
  756. if (!preg_match('/^\<.+@.+\>$/', $message)) {
  757. throw new SocketException(__d('cake_dev', 'Invalid format to Message-ID. The text should be something like "<uuid@server.com>"'));
  758. }
  759. $this->_messageId = $message;
  760. }
  761. return $this;
  762. }
  763. /**
  764. * Add attachments to the email message
  765. *
  766. * Attachments can be defined in a few forms depending on how much control you need:
  767. *
  768. * Attach a single file:
  769. *
  770. * {{{
  771. * $email->attachments('path/to/file');
  772. * }}}
  773. *
  774. * Attach a file with a different filename:
  775. *
  776. * {{{
  777. * $email->attachments(array('custom_name.txt' => 'path/to/file.txt'));
  778. * }}}
  779. *
  780. * Attach a file and specify additional properties:
  781. *
  782. * {{{
  783. * $email->attachments(array('custom_name.png' => array(
  784. * 'file' => 'path/to/file',
  785. * 'mimetype' => 'image/png',
  786. * 'contentId' => 'abc123'
  787. * ));
  788. * }}}
  789. *
  790. * The `contentId` key allows you to specify an inline attachment. In your email text, you
  791. * can use `<img src="cid:abc123" />` to display the image inline.
  792. *
  793. * @param mixed $attachments String with the filename or array with filenames
  794. * @return mixed Either the array of attachments when getting or $this when setting.
  795. * @throws SocketException
  796. */
  797. public function attachments($attachments = null) {
  798. if ($attachments === null) {
  799. return $this->_attachments;
  800. }
  801. $attach = array();
  802. foreach ((array)$attachments as $name => $fileInfo) {
  803. if (!is_array($fileInfo)) {
  804. $fileInfo = array('file' => $fileInfo);
  805. }
  806. if (!isset($fileInfo['file'])) {
  807. throw new SocketException(__d('cake_dev', 'File not specified.'));
  808. }
  809. $fileInfo['file'] = realpath($fileInfo['file']);
  810. if ($fileInfo['file'] === false || !file_exists($fileInfo['file'])) {
  811. throw new SocketException(__d('cake_dev', 'File not found: "%s"', $fileInfo['file']));
  812. }
  813. if (is_int($name)) {
  814. $name = basename($fileInfo['file']);
  815. }
  816. if (!isset($fileInfo['mimetype'])) {
  817. $fileInfo['mimetype'] = 'application/octet-stream';
  818. }
  819. $attach[$name] = $fileInfo;
  820. }
  821. $this->_attachments = $attach;
  822. return $this;
  823. }
  824. /**
  825. * Add attachments
  826. *
  827. * @param mixed $attachments String with the filename or array with filenames
  828. * @return CakeEmail $this
  829. * @throws SocketException
  830. */
  831. public function addAttachments($attachments) {
  832. $current = $this->_attachments;
  833. $this->attachments($attachments);
  834. $this->_attachments = array_merge($current, $this->_attachments);
  835. return $this;
  836. }
  837. /**
  838. * Get generated message (used by transport classes)
  839. *
  840. * @param mixed $type Use MESSAGE_* constants or null to return the full message as array
  841. * @return mixed String if have type, array if type is null
  842. */
  843. public function message($type = null) {
  844. switch ($type) {
  845. case self::MESSAGE_HTML:
  846. return $this->_htmlMessage;
  847. case self::MESSAGE_TEXT:
  848. return $this->_textMessage;
  849. }
  850. return $this->_message;
  851. }
  852. /**
  853. * Configuration to use when send email
  854. *
  855. * @param mixed $config String with configuration name (from email.php), array with config or null to return current config
  856. * @return mixed
  857. */
  858. public function config($config = null) {
  859. if ($config === null) {
  860. return $this->_config;
  861. }
  862. if (!is_array($config)) {
  863. $config = (string)$config;
  864. }
  865. $this->_applyConfig($config);
  866. return $this;
  867. }
  868. /**
  869. * Send an email using the specified content, template and layout
  870. *
  871. * @return array
  872. * @throws SocketException
  873. */
  874. public function send($content = null) {
  875. if (empty($this->_from)) {
  876. throw new SocketException(__d('cake_dev', 'From is not specified.'));
  877. }
  878. if (empty($this->_to) && empty($this->_cc) && empty($this->_bcc)) {
  879. throw new SocketException(__d('cake_dev', 'You need specify one destination on to, cc or bcc.'));
  880. }
  881. if (is_array($content)) {
  882. $content = implode("\n", $content) . "\n";
  883. }
  884. $this->_textMessage = $this->_htmlMessage = '';
  885. $this->_createBoundary();
  886. $this->_message = $this->_render($this->_wrap($content));
  887. $contents = $this->transportClass()->send($this);
  888. if (!empty($this->_config['log'])) {
  889. $level = LOG_DEBUG;
  890. if ($this->_config['log'] !== true) {
  891. $level = $this->_config['log'];
  892. }
  893. CakeLog::write($level, PHP_EOL . $contents['headers'] . PHP_EOL . $contents['message']);
  894. }
  895. return $contents;
  896. }
  897. /**
  898. * Static method to fast create an instance of CakeEmail
  899. *
  900. * @param mixed $to Address to send (see CakeEmail::to()). If null, will try to use 'to' from transport config
  901. * @param mixed $subject String of subject or null to use 'subject' from transport config
  902. * @param mixed $message String with message or array with variables to be used in render
  903. * @param mixed $transportConfig String to use config from EmailConfig or array with configs
  904. * @param boolean $send Send the email or just return the instance pre-configured
  905. * @return CakeEmail Instance of CakeEmail
  906. * @throws SocketException
  907. */
  908. public static function deliver($to = null, $subject = null, $message = null, $transportConfig = 'fast', $send = true) {
  909. $class = __CLASS__;
  910. $instance = new $class($transportConfig);
  911. if ($to !== null) {
  912. $instance->to($to);
  913. }
  914. if ($subject !== null) {
  915. $instance->subject($subject);
  916. }
  917. if (is_array($message)) {
  918. $instance->viewVars($message);
  919. $message = null;
  920. } elseif ($message === null && array_key_exists('message', $config = $instance->config())) {
  921. $message = $config['message'];
  922. }
  923. if ($send === true) {
  924. $instance->send($message);
  925. }
  926. return $instance;
  927. }
  928. /**
  929. * Apply the config to an instance
  930. *
  931. * @param CakeEmail $obj CakeEmail
  932. * @param array $config
  933. * @return void
  934. */
  935. protected function _applyConfig($config) {
  936. if (is_string($config)) {
  937. if (!class_exists('EmailConfig') && !config('email')) {
  938. throw new ConfigureException(__d('cake_dev', '%s not found.', APP . 'Config' . DS . 'email.php'));
  939. }
  940. $configs = new EmailConfig();
  941. if (!isset($configs->{$config})) {
  942. throw new ConfigureException(__d('cake_dev', 'Unknown email configuration "%s".', $config));
  943. }
  944. $config = $configs->{$config};
  945. }
  946. $this->_config += $config;
  947. if (!empty($config['charset'])) {
  948. $this->charset = $config['charset'];
  949. }
  950. if (!empty($config['headerCharset'])) {
  951. $this->headerCharset = $config['headerCharset'];
  952. }
  953. if (empty($this->headerCharset)) {
  954. $this->headerCharset = $this->charset;
  955. }
  956. $simpleMethods = array(
  957. 'from', 'sender', 'to', 'replyTo', 'readReceipt', 'returnPath', 'cc', 'bcc',
  958. 'messageId', 'subject', 'viewRender', 'viewVars', 'attachments',
  959. 'transport', 'emailFormat'
  960. );
  961. foreach ($simpleMethods as $method) {
  962. if (isset($config[$method])) {
  963. $this->$method($config[$method]);
  964. unset($config[$method]);
  965. }
  966. }
  967. if (isset($config['headers'])) {
  968. $this->setHeaders($config['headers']);
  969. unset($config['headers']);
  970. }
  971. if (array_key_exists('template', $config)) {
  972. $layout = false;
  973. if (array_key_exists('layout', $config)) {
  974. $layout = $config['layout'];
  975. unset($config['layout']);
  976. }
  977. $this->template($config['template'], $layout);
  978. unset($config['template']);
  979. }
  980. $this->transportClass()->config($config);
  981. }
  982. /**
  983. * Reset all EmailComponent internal variables to be able to send out a new email.
  984. *
  985. * @return CakeEmail $this
  986. */
  987. public function reset() {
  988. $this->_to = array();
  989. $this->_from = array();
  990. $this->_sender = array();
  991. $this->_replyTo = array();
  992. $this->_readReceipt = array();
  993. $this->_returnPath = array();
  994. $this->_cc = array();
  995. $this->_bcc = array();
  996. $this->_messageId = true;
  997. $this->_subject = '';
  998. $this->_headers = array();
  999. $this->_layout = 'default';
  1000. $this->_template = '';
  1001. $this->_viewRender = 'View';
  1002. $this->_viewVars = array();
  1003. $this->_helpers = array('Html');
  1004. $this->_textMessage = '';
  1005. $this->_htmlMessage = '';
  1006. $this->_message = '';
  1007. $this->_emailFormat = 'text';
  1008. $this->_transportName = 'Mail';
  1009. $this->_transportClass = null;
  1010. $this->_attachments = array();
  1011. $this->_config = array();
  1012. return $this;
  1013. }
  1014. /**
  1015. * Encode the specified string using the current charset
  1016. *
  1017. * @param string $text String to encode
  1018. * @return string Encoded string
  1019. */
  1020. protected function _encode($text) {
  1021. $internalEncoding = function_exists('mb_internal_encoding');
  1022. if ($internalEncoding) {
  1023. $restore = mb_internal_encoding();
  1024. mb_internal_encoding($this->_appCharset);
  1025. }
  1026. $return = mb_encode_mimeheader($text, $this->headerCharset, 'B');
  1027. if ($internalEncoding) {
  1028. mb_internal_encoding($restore);
  1029. }
  1030. return $return;
  1031. }
  1032. /**
  1033. * Translates a string for one charset to another if the App.encoding value
  1034. * differs and the mb_convert_encoding function exists
  1035. *
  1036. * @param string $text The text to be converted
  1037. * @param string $charset the target encoding
  1038. * @return string
  1039. */
  1040. protected function _encodeString($text, $charset) {
  1041. if ($this->_appCharset === $charset || !function_exists('mb_convert_encoding')) {
  1042. return $text;
  1043. }
  1044. return mb_convert_encoding($text, $charset, $this->_appCharset);
  1045. }
  1046. /**
  1047. * Wrap the message to follow the RFC 2822 - 2.1.1
  1048. *
  1049. * @param string $message Message to wrap
  1050. * @return array Wrapped message
  1051. */
  1052. protected function _wrap($message) {
  1053. $message = str_replace(array("\r\n", "\r"), "\n", $message);
  1054. $lines = explode("\n", $message);
  1055. $formatted = array();
  1056. foreach ($lines as $line) {
  1057. if (empty($line)) {
  1058. $formatted[] = '';
  1059. continue;
  1060. }
  1061. if ($line[0] === '.') {
  1062. $line = '.' . $line;
  1063. }
  1064. if (!preg_match('/\<[a-z]/i', $line)) {
  1065. $formatted = array_merge($formatted, explode("\n", wordwrap($line, self::LINE_LENGTH_SHOULD, "\n")));
  1066. continue;
  1067. }
  1068. $tagOpen = false;
  1069. $tmpLine = $tag = '';
  1070. $tmpLineLength = 0;
  1071. for ($i = 0, $count = strlen($line); $i < $count; $i++) {
  1072. $char = $line[$i];
  1073. if ($tagOpen) {
  1074. $tag .= $char;
  1075. if ($char === '>') {
  1076. $tagLength = strlen($tag);
  1077. if ($tagLength + $tmpLineLength < self::LINE_LENGTH_SHOULD) {
  1078. $tmpLine .= $tag;
  1079. $tmpLineLength += $tagLength;
  1080. } else {
  1081. if ($tmpLineLength > 0) {
  1082. $formatted[] = trim($tmpLine);
  1083. $tmpLine = '';
  1084. $tmpLineLength = 0;
  1085. }
  1086. if ($tagLength > self::LINE_LENGTH_SHOULD) {
  1087. $formatted[] = $tag;
  1088. } else {
  1089. $tmpLine = $tag;
  1090. $tmpLineLength = $tagLength;
  1091. }
  1092. }
  1093. $tag = '';
  1094. $tagOpen = false;
  1095. }
  1096. continue;
  1097. }
  1098. if ($char === '<') {
  1099. $tagOpen = true;
  1100. $tag = '<';
  1101. continue;
  1102. }
  1103. if ($char === ' ' && $tmpLineLength >= self::LINE_LENGTH_SHOULD) {
  1104. $formatted[] = $tmpLine;
  1105. $tmpLineLength = 0;
  1106. continue;
  1107. }
  1108. $tmpLine .= $char;
  1109. $tmpLineLength++;
  1110. if ($tmpLineLength === self::LINE_LENGTH_SHOULD) {
  1111. $nextChar = $line[$i + 1];
  1112. if ($nextChar === ' ' || $nextChar === '<') {
  1113. $formatted[] = trim($tmpLine);
  1114. $tmpLine = '';
  1115. $tmpLineLength = 0;
  1116. if ($nextChar === ' ') {
  1117. $i++;
  1118. }
  1119. } else {
  1120. $lastSpace = strrpos($tmpLine, ' ');
  1121. if ($lastSpace === false) {
  1122. continue;
  1123. }
  1124. $formatted[] = trim(substr($tmpLine, 0, $lastSpace));
  1125. $tmpLine = substr($tmpLine, $lastSpace + 1);
  1126. $tmpLineLength = strlen($tmpLine);
  1127. }
  1128. }
  1129. }
  1130. if (!empty($tmpLine)) {
  1131. $formatted[] = $tmpLine;
  1132. }
  1133. }
  1134. $formatted[] = '';
  1135. return $formatted;
  1136. }
  1137. /**
  1138. * Create unique boundary identifier
  1139. *
  1140. * @return void
  1141. */
  1142. protected function _createBoundary() {
  1143. if (!empty($this->_attachments) || $this->_emailFormat === 'both') {
  1144. $this->_boundary = md5(uniqid(time()));
  1145. }
  1146. }
  1147. /**
  1148. * Attach non-embedded files by adding file contents inside boundaries.
  1149. *
  1150. * @return array An array of lines to add to the message
  1151. */
  1152. protected function _attachFiles() {
  1153. $msg = array();
  1154. foreach ($this->_attachments as $filename => $fileInfo) {
  1155. if (!empty($fileInfo['contentId'])) {
  1156. continue;
  1157. }
  1158. $data = $this->_readFile($fileInfo['file']);
  1159. $msg[] = '--' . $this->_boundary;
  1160. $msg[] = 'Content-Type: ' . $fileInfo['mimetype'];
  1161. $msg[] = 'Content-Transfer-Encoding: base64';
  1162. $msg[] = 'Content-Disposition: attachment; filename="' . $filename . '"';
  1163. $msg[] = '';
  1164. $msg[] = $data;
  1165. $msg[] = '';
  1166. }
  1167. return $msg;
  1168. }
  1169. /**
  1170. * Read the file contents and return a base64 version of the file contents.
  1171. *
  1172. * @param string $file The file to read.
  1173. * @return string File contents in base64 encoding
  1174. */
  1175. protected function _readFile($file) {
  1176. $handle = fopen($file, 'rb');
  1177. $data = fread($handle, filesize($file));
  1178. $data = chunk_split(base64_encode($data)) ;
  1179. fclose($handle);
  1180. return $data;
  1181. }
  1182. /**
  1183. * Attach inline/embedded files to the message.
  1184. *
  1185. * @return array An array of lines to add to the message
  1186. */
  1187. protected function _attachInlineFiles() {
  1188. $msg = array();
  1189. foreach ($this->_attachments as $filename => $fileInfo) {
  1190. if (empty($fileInfo['contentId'])) {
  1191. continue;
  1192. }
  1193. $data = $this->_readFile($fileInfo['file']);
  1194. $msg[] = '--' . $this->_boundary;
  1195. $msg[] = 'Content-Type: ' . $fileInfo['mimetype'];
  1196. $msg[] = 'Content-Transfer-Encoding: base64';
  1197. $msg[] = 'Content-ID: <' . $fileInfo['contentId'] . '>';
  1198. $msg[] = 'Content-Disposition: inline; filename="' . $filename . '"';
  1199. $msg[] = '';
  1200. $msg[] = $data;
  1201. $msg[] = '';
  1202. }
  1203. return $msg;
  1204. }
  1205. /**
  1206. * Render the body of the email.
  1207. *
  1208. * @param string $content Content to render
  1209. * @return array Email body ready to be sent
  1210. */
  1211. protected function _render($content) {
  1212. $content = implode("\n", $content);
  1213. $rendered = $this->_renderTemplates($content);
  1214. $msg = array();
  1215. $contentIds = array_filter((array)Set::classicExtract($this->_attachments, '{s}.contentId'));
  1216. $hasInlineAttachments = count($contentIds) > 0;
  1217. $hasAttachments = !empty($this->_attachments);
  1218. $hasMultipleTypes = count($rendered) > 1;
  1219. $boundary = $relBoundary = $textBoundary = $this->_boundary;
  1220. if ($hasInlineAttachments) {
  1221. $msg[] = '--' . $boundary;
  1222. $msg[] = 'Content-Type: multipart/related; boundary="rel-' . $boundary . '"';
  1223. $msg[] = '';
  1224. $relBoundary = 'rel-' . $boundary;
  1225. }
  1226. if ($hasMultipleTypes) {
  1227. $msg[] = '--' . $relBoundary;
  1228. $msg[] = 'Content-Type: multipart/alternative; boundary="alt-' . $boundary . '"';
  1229. $msg[] = '';
  1230. $textBoundary = 'alt-' . $boundary;
  1231. }
  1232. if (isset($rendered['text'])) {
  1233. if ($textBoundary !== $boundary || $hasAttachments) {
  1234. $msg[] = '--' . $textBoundary;
  1235. $msg[] = 'Content-Type: text/plain; charset=' . $this->charset;
  1236. $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding();
  1237. $msg[] = '';
  1238. }
  1239. $this->_textMessage = $rendered['text'];
  1240. $content = explode("\n", $this->_textMessage);
  1241. $msg = array_merge($msg, $content);
  1242. $msg[] = '';
  1243. }
  1244. if (isset($rendered['html'])) {
  1245. if ($textBoundary !== $boundary || $hasAttachments) {
  1246. $msg[] = '--' . $textBoundary;
  1247. $msg[] = 'Content-Type: text/html; charset=' . $this->charset;
  1248. $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding();
  1249. $msg[] = '';
  1250. }
  1251. $this->_htmlMessage = $rendered['html'];
  1252. $content = explode("\n", $this->_htmlMessage);
  1253. $msg = array_merge($msg, $content);
  1254. $msg[] = '';
  1255. }
  1256. if ($hasMultipleTypes) {
  1257. $msg[] = '--' . $textBoundary . '--';
  1258. $msg[] = '';
  1259. }
  1260. if ($hasInlineAttachments) {
  1261. $attachments = $this->_attachInlineFiles();
  1262. $msg = array_merge($msg, $attachments);
  1263. $msg[] = '';
  1264. $msg[] = '--' . $relBoundary . '--';
  1265. $msg[] = '';
  1266. }
  1267. if ($hasAttachments) {
  1268. $attachments = $this->_attachFiles();
  1269. $msg = array_merge($msg, $attachments);
  1270. }
  1271. if ($hasAttachments || $hasMultipleTypes) {
  1272. $msg[] = '';
  1273. $msg[] = '--' . $boundary . '--';
  1274. $msg[] = '';
  1275. }
  1276. return $msg;
  1277. }
  1278. /**
  1279. * Gets the text body types that are in this email message
  1280. *
  1281. * @return array Array of types. Valid types are 'text' and 'html'
  1282. */
  1283. protected function _getTypes() {
  1284. $types = array($this->_emailFormat);
  1285. if ($this->_emailFormat == 'both') {
  1286. $types = array('html', 'text');
  1287. }
  1288. return $types;
  1289. }
  1290. /**
  1291. * Build and set all the view properties needed to render the templated emails.
  1292. * If there is no template set, the $content will be returned in a hash
  1293. * of the text content types for the email.
  1294. *
  1295. * @param string $content The content passed in from send() in most cases.
  1296. * @return array The rendered content with html and text keys.
  1297. */
  1298. protected function _renderTemplates($content) {
  1299. $types = $this->_getTypes();
  1300. $rendered = array();
  1301. if (empty($this->_template)) {
  1302. foreach ($types as $type) {
  1303. $rendered[$type] = $this->_encodeString($content, $this->charset);
  1304. }
  1305. return $rendered;
  1306. }
  1307. $viewClass = $this->_viewRender;
  1308. if ($viewClass !== 'View') {
  1309. list($plugin, $viewClass) = pluginSplit($viewClass, true);
  1310. $viewClass .= 'View';
  1311. App::uses($viewClass, $plugin . 'View');
  1312. }
  1313. $View = new $viewClass(null);
  1314. $View->viewVars = $this->_viewVars;
  1315. $View->helpers = $this->_helpers;
  1316. list($templatePlugin, $template) = pluginSplit($this->_template);
  1317. list($layoutPlugin, $layout) = pluginSplit($this->_layout);
  1318. if ($templatePlugin) {
  1319. $View->plugin = $templatePlugin;
  1320. } elseif ($layoutPlugin) {
  1321. $View->plugin = $layoutPlugin;
  1322. }
  1323. foreach ($types as $type) {
  1324. $View->set('content', $content);
  1325. $View->hasRendered = false;
  1326. $View->viewPath = $View->layoutPath = 'Emails' . DS . $type;
  1327. $render = $View->render($template, $layout);
  1328. $render = str_replace(array("\r\n", "\r"), "\n", $render);
  1329. $rendered[$type] = $this->_encodeString($render, $this->charset);
  1330. }
  1331. return $rendered;
  1332. }
  1333. /**
  1334. * Return the Content-Transfer Encoding value based on the set charset
  1335. *
  1336. * @return void
  1337. */
  1338. protected function _getContentTransferEncoding() {
  1339. $charset = strtoupper($this->charset);
  1340. if (in_array($charset, $this->_charset8bit)) {
  1341. return '8bit';
  1342. }
  1343. return '7bit';
  1344. }
  1345. }