PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/system/libraries/Email.php

https://github.com/betchi/CodeIgniter
PHP | 2259 lines | 1245 code | 331 blank | 683 comment | 128 complexity | d6dfb669ee48e94ce05cfea7f5091e00 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. <?php
  2. /**
  3. * CodeIgniter
  4. *
  5. * An open source application development framework for PHP 5.2.4 or newer
  6. *
  7. * NOTICE OF LICENSE
  8. *
  9. * Licensed under the Open Software License version 3.0
  10. *
  11. * This source file is subject to the Open Software License (OSL 3.0) that is
  12. * bundled with this package in the files license.txt / license.rst. It is
  13. * also available through the world wide web at this URL:
  14. * http://opensource.org/licenses/OSL-3.0
  15. * If you did not receive a copy of the license and are unable to obtain it
  16. * through the world wide web, please send an email to
  17. * licensing@ellislab.com so we can send you a copy immediately.
  18. *
  19. * @package CodeIgniter
  20. * @author EllisLab Dev Team
  21. * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
  22. * @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
  23. * @link http://codeigniter.com
  24. * @since Version 1.0
  25. * @filesource
  26. */
  27. defined('BASEPATH') OR exit('No direct script access allowed');
  28. /**
  29. * CodeIgniter Email Class
  30. *
  31. * Permits email to be sent using Mail, Sendmail, or SMTP.
  32. *
  33. * @package CodeIgniter
  34. * @subpackage Libraries
  35. * @category Libraries
  36. * @author EllisLab Dev Team
  37. * @link http://codeigniter.com/user_guide/libraries/email.html
  38. */
  39. class CI_Email {
  40. /**
  41. * Used as the User-Agent and X-Mailer headers' value.
  42. *
  43. * @var string
  44. */
  45. public $useragent = 'CodeIgniter';
  46. /**
  47. * Path to the Sendmail binary.
  48. *
  49. * @var string
  50. */
  51. public $mailpath = '/usr/sbin/sendmail'; // Sendmail path
  52. /**
  53. * Which method to use for sending e-mails.
  54. *
  55. * @var string 'mail', 'sendmail' or 'smtp'
  56. */
  57. public $protocol = 'mail'; // mail/sendmail/smtp
  58. /**
  59. * STMP Server host
  60. *
  61. * @var string
  62. */
  63. public $smtp_host = '';
  64. /**
  65. * SMTP Username
  66. *
  67. * @var string
  68. */
  69. public $smtp_user = '';
  70. /**
  71. * SMTP Password
  72. *
  73. * @var string
  74. */
  75. public $smtp_pass = '';
  76. /**
  77. * SMTP Server port
  78. *
  79. * @var int
  80. */
  81. public $smtp_port = 25;
  82. /**
  83. * SMTP connection timeout in seconds
  84. *
  85. * @var int
  86. */
  87. public $smtp_timeout = 5;
  88. /**
  89. * SMTP persistent connection
  90. *
  91. * @var bool
  92. */
  93. public $smtp_keepalive = FALSE;
  94. /**
  95. * SMTP Encryption
  96. *
  97. * @var string empty, 'tls' or 'ssl'
  98. */
  99. public $smtp_crypto = '';
  100. /**
  101. * Whether to apply word-wrapping to the message body.
  102. *
  103. * @var bool
  104. */
  105. public $wordwrap = TRUE;
  106. /**
  107. * Number of characters to wrap at.
  108. *
  109. * @see CI_Email::$wordwrap
  110. * @var int
  111. */
  112. public $wrapchars = 76;
  113. /**
  114. * Message format.
  115. *
  116. * @var string 'text' or 'html'
  117. */
  118. public $mailtype = 'text';
  119. /**
  120. * Character set (default: utf-8)
  121. *
  122. * @var string
  123. */
  124. public $charset = 'utf-8';
  125. /**
  126. * Multipart message
  127. *
  128. * @var string 'mixed' (in the body) or 'related' (separate)
  129. */
  130. public $multipart = 'mixed'; // "mixed" (in the body) or "related" (separate)
  131. /**
  132. * Alternative message (for HTML messages only)
  133. *
  134. * @var string
  135. */
  136. public $alt_message = '';
  137. /**
  138. * Whether to validate e-mail addresses.
  139. *
  140. * @var bool
  141. */
  142. public $validate = FALSE;
  143. /**
  144. * X-Priority header value.
  145. *
  146. * @var int 1-5
  147. */
  148. public $priority = 3; // Default priority (1 - 5)
  149. /**
  150. * Newline character sequence.
  151. * Use "\r\n" to comply with RFC 822.
  152. *
  153. * @link http://www.ietf.org/rfc/rfc822.txt
  154. * @var string "\r\n" or "\n"
  155. */
  156. public $newline = "\n"; // Default newline. "\r\n" or "\n" (Use "\r\n" to comply with RFC 822)
  157. /**
  158. * CRLF character sequence
  159. *
  160. * RFC 2045 specifies that for 'quoted-printable' encoding,
  161. * "\r\n" must be used. However, it appears that some servers
  162. * (even on the receiving end) don't handle it properly and
  163. * switching to "\n", while improper, is the only solution
  164. * that seems to work for all environments.
  165. *
  166. * @link http://www.ietf.org/rfc/rfc822.txt
  167. * @var string
  168. */
  169. public $crlf = "\n";
  170. /**
  171. * Whether to use Delivery Status Notification.
  172. *
  173. * @var bool
  174. */
  175. public $dsn = FALSE;
  176. /**
  177. * Whether to send multipart alternatives.
  178. * Yahoo! doesn't seem to like these.
  179. *
  180. * @var bool
  181. */
  182. public $send_multipart = TRUE;
  183. /**
  184. * Whether to send messages to BCC recipients in batches.
  185. *
  186. * @var bool
  187. */
  188. public $bcc_batch_mode = FALSE;
  189. /**
  190. * BCC Batch max number size.
  191. *
  192. * @see CI_Email::$bcc_batch_mode
  193. * @var int
  194. */
  195. public $bcc_batch_size = 200;
  196. // --------------------------------------------------------------------
  197. /**
  198. * Whether PHP is running in safe mode. Initialized by the class constructor.
  199. *
  200. * @var bool
  201. */
  202. protected $_safe_mode = FALSE;
  203. /**
  204. * Subject header
  205. *
  206. * @var string
  207. */
  208. protected $_subject = '';
  209. /**
  210. * Message body
  211. *
  212. * @var string
  213. */
  214. protected $_body = '';
  215. /**
  216. * Final message body to be sent.
  217. *
  218. * @var string
  219. */
  220. protected $_finalbody = '';
  221. /**
  222. * multipart/alternative boundary
  223. *
  224. * @var string
  225. */
  226. protected $_alt_boundary = '';
  227. /**
  228. * Attachment boundary
  229. *
  230. * @var string
  231. */
  232. protected $_atc_boundary = '';
  233. /**
  234. * Final headers to send
  235. *
  236. * @var string
  237. */
  238. protected $_header_str = '';
  239. /**
  240. * SMTP Connection socket placeholder
  241. *
  242. * @var resource
  243. */
  244. protected $_smtp_connect = '';
  245. /**
  246. * Mail encoding
  247. *
  248. * @var string '8bit' or '7bit'
  249. */
  250. protected $_encoding = '8bit';
  251. /**
  252. * Whether to perform SMTP authentication
  253. *
  254. * @var bool
  255. */
  256. protected $_smtp_auth = FALSE;
  257. /**
  258. * Whether to send a Reply-To header
  259. *
  260. * @var bool
  261. */
  262. protected $_replyto_flag = FALSE;
  263. /**
  264. * Debug messages
  265. *
  266. * @see CI_Email::print_debugger()
  267. * @var string
  268. */
  269. protected $_debug_msg = array();
  270. /**
  271. * Recipients
  272. *
  273. * @var string[]
  274. */
  275. protected $_recipients = array();
  276. /**
  277. * CC Recipients
  278. *
  279. * @var string[]
  280. */
  281. protected $_cc_array = array();
  282. /**
  283. * BCC Recipients
  284. *
  285. * @var string[]
  286. */
  287. protected $_bcc_array = array();
  288. /**
  289. * Message headers
  290. *
  291. * @var string[]
  292. */
  293. protected $_headers = array();
  294. /**
  295. * Attachment data
  296. *
  297. * @var array
  298. */
  299. protected $_attachments = array();
  300. /**
  301. * Valid $protocol values
  302. *
  303. * @see CI_Email::$protocol
  304. * @var string[]
  305. */
  306. protected $_protocols = array('mail', 'sendmail', 'smtp');
  307. /**
  308. * Base charsets
  309. *
  310. * Character sets valid for 7-bit encoding,
  311. * excluding language suffix.
  312. *
  313. * @var string[]
  314. */
  315. protected $_base_charsets = array('us-ascii', 'iso-2022-');
  316. /**
  317. * Bit depths
  318. *
  319. * Valid mail encodings
  320. *
  321. * @see CI_Email::$_encoding
  322. * @var string[]
  323. */
  324. protected $_bit_depths = array('7bit', '8bit');
  325. /**
  326. * $priority translations
  327. *
  328. * Actual values to send with the X-Priority header
  329. *
  330. * @var string[]
  331. */
  332. protected $_priorities = array(
  333. 1 => '1 (Highest)',
  334. 2 => '2 (High)',
  335. 3 => '3 (Normal)',
  336. 4 => '4 (Low)',
  337. 5 => '5 (Lowest)'
  338. );
  339. // --------------------------------------------------------------------
  340. /**
  341. * Constructor - Sets Email Preferences
  342. *
  343. * The constructor can be passed an array of config values
  344. *
  345. * @param array $config = array()
  346. * @return void
  347. */
  348. public function __construct($config = array())
  349. {
  350. $this->charset = config_item('charset');
  351. if (count($config) > 0)
  352. {
  353. $this->initialize($config);
  354. }
  355. else
  356. {
  357. $this->_smtp_auth = ! ($this->smtp_user === '' && $this->smtp_pass === '');
  358. }
  359. $this->_safe_mode = ( ! is_php('5.4') && ini_get('safe_mode'));
  360. $this->charset = strtoupper($this->charset);
  361. log_message('debug', 'Email Class Initialized');
  362. }
  363. // --------------------------------------------------------------------
  364. /**
  365. * Destructor - Releases Resources
  366. *
  367. * @return void
  368. */
  369. public function __destruct()
  370. {
  371. if (is_resource($this->_smtp_connect))
  372. {
  373. $this->_send_command('quit');
  374. }
  375. }
  376. // --------------------------------------------------------------------
  377. /**
  378. * Initialize preferences
  379. *
  380. * @param array
  381. * @return CI_Email
  382. */
  383. public function initialize($config = array())
  384. {
  385. foreach ($config as $key => $val)
  386. {
  387. if (isset($this->$key))
  388. {
  389. $method = 'set_'.$key;
  390. if (method_exists($this, $method))
  391. {
  392. $this->$method($val);
  393. }
  394. else
  395. {
  396. $this->$key = $val;
  397. }
  398. }
  399. }
  400. $this->clear();
  401. $this->_smtp_auth = ! ($this->smtp_user === '' && $this->smtp_pass === '');
  402. return $this;
  403. }
  404. // --------------------------------------------------------------------
  405. /**
  406. * Initialize the Email Data
  407. *
  408. * @param bool
  409. * @return CI_Email
  410. */
  411. public function clear($clear_attachments = FALSE)
  412. {
  413. $this->_subject = '';
  414. $this->_body = '';
  415. $this->_finalbody = '';
  416. $this->_header_str = '';
  417. $this->_replyto_flag = FALSE;
  418. $this->_recipients = array();
  419. $this->_cc_array = array();
  420. $this->_bcc_array = array();
  421. $this->_headers = array();
  422. $this->_debug_msg = array();
  423. $this->set_header('User-Agent', $this->useragent);
  424. $this->set_header('Date', $this->_set_date());
  425. if ($clear_attachments !== FALSE)
  426. {
  427. $this->_attachments = array();
  428. }
  429. return $this;
  430. }
  431. // --------------------------------------------------------------------
  432. /**
  433. * Set FROM
  434. *
  435. * @param string $from
  436. * @param string $name
  437. * @param string $return_path = NULL Return-Path
  438. * @return CI_Email
  439. */
  440. public function from($from, $name = '', $return_path = NULL)
  441. {
  442. if (preg_match('/\<(.*)\>/', $from, $match))
  443. {
  444. $from = $match[1];
  445. }
  446. if ($this->validate)
  447. {
  448. $this->validate_email($this->_str_to_array($from));
  449. if ($return_path)
  450. {
  451. $this->validate_email($this->_str_to_array($return_path));
  452. }
  453. }
  454. // prepare the display name
  455. if ($name !== '')
  456. {
  457. // only use Q encoding if there are characters that would require it
  458. if ( ! preg_match('/[\200-\377]/', $name))
  459. {
  460. // add slashes for non-printing characters, slashes, and double quotes, and surround it in double quotes
  461. $name = '"'.addcslashes($name, "\0..\37\177'\"\\").'"';
  462. }
  463. else
  464. {
  465. $name = $this->_prep_q_encoding($name);
  466. }
  467. }
  468. $this->set_header('From', $name.' <'.$from.'>');
  469. isset($return_path) OR $return_path = $from;
  470. $this->set_header('Return-Path', '<'.$return_path.'>');
  471. return $this;
  472. }
  473. // --------------------------------------------------------------------
  474. /**
  475. * Set Reply-to
  476. *
  477. * @param string
  478. * @param string
  479. * @return CI_Email
  480. */
  481. public function reply_to($replyto, $name = '')
  482. {
  483. if (preg_match('/\<(.*)\>/', $replyto, $match))
  484. {
  485. $replyto = $match[1];
  486. }
  487. if ($this->validate)
  488. {
  489. $this->validate_email($this->_str_to_array($replyto));
  490. }
  491. if ($name === '')
  492. {
  493. $name = $replyto;
  494. }
  495. if (strpos($name, '"') !== 0)
  496. {
  497. $name = '"'.$name.'"';
  498. }
  499. $this->set_header('Reply-To', $name.' <'.$replyto.'>');
  500. $this->_replyto_flag = TRUE;
  501. return $this;
  502. }
  503. // --------------------------------------------------------------------
  504. /**
  505. * Set Recipients
  506. *
  507. * @param string
  508. * @return CI_Email
  509. */
  510. public function to($to)
  511. {
  512. $to = $this->_str_to_array($to);
  513. $to = $this->clean_email($to);
  514. if ($this->validate)
  515. {
  516. $this->validate_email($to);
  517. }
  518. if ($this->_get_protocol() !== 'mail')
  519. {
  520. $this->set_header('To', implode(', ', $to));
  521. }
  522. $this->_recipients = $to;
  523. return $this;
  524. }
  525. // --------------------------------------------------------------------
  526. /**
  527. * Set CC
  528. *
  529. * @param string
  530. * @return CI_Email
  531. */
  532. public function cc($cc)
  533. {
  534. $cc = $this->clean_email($this->_str_to_array($cc));
  535. if ($this->validate)
  536. {
  537. $this->validate_email($cc);
  538. }
  539. $this->set_header('Cc', implode(', ', $cc));
  540. if ($this->_get_protocol() === 'smtp')
  541. {
  542. $this->_cc_array = $cc;
  543. }
  544. return $this;
  545. }
  546. // --------------------------------------------------------------------
  547. /**
  548. * Set BCC
  549. *
  550. * @param string
  551. * @param string
  552. * @return CI_Email
  553. */
  554. public function bcc($bcc, $limit = '')
  555. {
  556. if ($limit !== '' && is_numeric($limit))
  557. {
  558. $this->bcc_batch_mode = TRUE;
  559. $this->bcc_batch_size = $limit;
  560. }
  561. $bcc = $this->clean_email($this->_str_to_array($bcc));
  562. if ($this->validate)
  563. {
  564. $this->validate_email($bcc);
  565. }
  566. if ($this->_get_protocol() === 'smtp' OR ($this->bcc_batch_mode && count($bcc) > $this->bcc_batch_size))
  567. {
  568. $this->_bcc_array = $bcc;
  569. }
  570. else
  571. {
  572. $this->set_header('Bcc', implode(', ', $bcc));
  573. }
  574. return $this;
  575. }
  576. // --------------------------------------------------------------------
  577. /**
  578. * Set Email Subject
  579. *
  580. * @param string
  581. * @return CI_Email
  582. */
  583. public function subject($subject)
  584. {
  585. $subject = $this->_prep_q_encoding($subject);
  586. $this->set_header('Subject', $subject);
  587. return $this;
  588. }
  589. // --------------------------------------------------------------------
  590. /**
  591. * Set Body
  592. *
  593. * @param string
  594. * @return CI_Email
  595. */
  596. public function message($body)
  597. {
  598. $this->_body = rtrim(str_replace("\r", '', $body));
  599. /* strip slashes only if magic quotes is ON
  600. if we do it with magic quotes OFF, it strips real, user-inputted chars.
  601. NOTE: In PHP 5.4 get_magic_quotes_gpc() will always return 0 and
  602. it will probably not exist in future versions at all.
  603. */
  604. if ( ! is_php('5.4') && get_magic_quotes_gpc())
  605. {
  606. $this->_body = stripslashes($this->_body);
  607. }
  608. return $this;
  609. }
  610. // --------------------------------------------------------------------
  611. /**
  612. * Assign file attachments
  613. *
  614. * @param string $file Can be local path, URL or buffered content
  615. * @param string $disposition = 'attachment'
  616. * @param string $newname = NULL
  617. * @param string $mime = ''
  618. * @return CI_Email
  619. */
  620. public function attach($file, $disposition = '', $newname = NULL, $mime = '')
  621. {
  622. if ($mime === '')
  623. {
  624. if (strpos($file, '://') === FALSE && ! file_exists($file))
  625. {
  626. $this->_set_error_message('lang:email_attachment_missing', $file);
  627. return FALSE;
  628. }
  629. if ( ! $fp = @fopen($file, 'rb'))
  630. {
  631. $this->_set_error_message('lang:email_attachment_unreadable', $file);
  632. return FALSE;
  633. }
  634. $file_content = stream_get_contents($fp);
  635. $mime = $this->_mime_types(pathinfo($file, PATHINFO_EXTENSION));
  636. fclose($fp);
  637. }
  638. else
  639. {
  640. $file_content =& $file; // buffered file
  641. }
  642. $this->_attachments[] = array(
  643. 'name' => array($file, $newname),
  644. 'disposition' => empty($disposition) ? 'attachment' : $disposition, // Can also be 'inline' Not sure if it matters
  645. 'type' => $mime,
  646. 'content' => chunk_split(base64_encode($file_content))
  647. );
  648. return $this;
  649. }
  650. // --------------------------------------------------------------------
  651. /**
  652. * Set and return attachment Content-ID
  653. *
  654. * Useful for attached inline pictures
  655. *
  656. * @param string $filename
  657. * @return string
  658. */
  659. public function attachment_cid($filename)
  660. {
  661. if ($this->multipart !== 'related')
  662. {
  663. $this->multipart = 'related'; // Thunderbird need this for inline images
  664. }
  665. for ($i = 0, $c = count($this->_attachments); $i < $c; $i++)
  666. {
  667. if ($this->_attachments[$i]['name'][0] === $filename)
  668. {
  669. $this->_attachments[$i]['cid'] = uniqid(basename($this->_attachments[$i]['name'][0]).'@');
  670. return $this->_attachments[$i]['cid'];
  671. }
  672. }
  673. return FALSE;
  674. }
  675. // --------------------------------------------------------------------
  676. /**
  677. * Add a Header Item
  678. *
  679. * @param string
  680. * @param string
  681. * @return void
  682. */
  683. public function set_header($header, $value)
  684. {
  685. $this->_headers[$header] = str_replace(array("\n", "\r"), '', $value);
  686. }
  687. // --------------------------------------------------------------------
  688. /**
  689. * Convert a String to an Array
  690. *
  691. * @param string
  692. * @return array
  693. */
  694. protected function _str_to_array($email)
  695. {
  696. if ( ! is_array($email))
  697. {
  698. return (strpos($email, ',') !== FALSE)
  699. ? preg_split('/[\s,]/', $email, -1, PREG_SPLIT_NO_EMPTY)
  700. : (array) trim($email);
  701. }
  702. return $email;
  703. }
  704. // --------------------------------------------------------------------
  705. /**
  706. * Set Multipart Value
  707. *
  708. * @param string
  709. * @return CI_Email
  710. */
  711. public function set_alt_message($str)
  712. {
  713. $this->alt_message = (string) $str;
  714. return $this;
  715. }
  716. // --------------------------------------------------------------------
  717. /**
  718. * Set Mailtype
  719. *
  720. * @param string
  721. * @return CI_Email
  722. */
  723. public function set_mailtype($type = 'text')
  724. {
  725. $this->mailtype = ($type === 'html') ? 'html' : 'text';
  726. return $this;
  727. }
  728. // --------------------------------------------------------------------
  729. /**
  730. * Set Wordwrap
  731. *
  732. * @param bool
  733. * @return CI_Email
  734. */
  735. public function set_wordwrap($wordwrap = TRUE)
  736. {
  737. $this->wordwrap = (bool) $wordwrap;
  738. return $this;
  739. }
  740. // --------------------------------------------------------------------
  741. /**
  742. * Set Protocol
  743. *
  744. * @param string
  745. * @return CI_Email
  746. */
  747. public function set_protocol($protocol = 'mail')
  748. {
  749. $this->protocol = in_array($protocol, $this->_protocols, TRUE) ? strtolower($protocol) : 'mail';
  750. return $this;
  751. }
  752. // --------------------------------------------------------------------
  753. /**
  754. * Set Priority
  755. *
  756. * @param int
  757. * @return CI_Email
  758. */
  759. public function set_priority($n = 3)
  760. {
  761. $this->priority = preg_match('/^[1-5]$/', $n) ? (int) $n : 3;
  762. return $this;
  763. }
  764. // --------------------------------------------------------------------
  765. /**
  766. * Set Newline Character
  767. *
  768. * @param string
  769. * @return CI_Email
  770. */
  771. public function set_newline($newline = "\n")
  772. {
  773. $this->newline = in_array($newline, array("\n", "\r\n", "\r")) ? $newline : "\n";
  774. return $this;
  775. }
  776. // --------------------------------------------------------------------
  777. /**
  778. * Set CRLF
  779. *
  780. * @param string
  781. * @return CI_Email
  782. */
  783. public function set_crlf($crlf = "\n")
  784. {
  785. $this->crlf = ($crlf !== "\n" && $crlf !== "\r\n" && $crlf !== "\r") ? "\n" : $crlf;
  786. return $this;
  787. }
  788. // --------------------------------------------------------------------
  789. /**
  790. * Set Message Boundary
  791. *
  792. * @return void
  793. */
  794. protected function _set_boundaries()
  795. {
  796. $this->_alt_boundary = 'B_ALT_'.uniqid(''); // multipart/alternative
  797. $this->_atc_boundary = 'B_ATC_'.uniqid(''); // attachment boundary
  798. }
  799. // --------------------------------------------------------------------
  800. /**
  801. * Get the Message ID
  802. *
  803. * @return string
  804. */
  805. protected function _get_message_id()
  806. {
  807. $from = str_replace(array('>', '<'), '', $this->_headers['Return-Path']);
  808. return '<'.uniqid('').strstr($from, '@').'>';
  809. }
  810. // --------------------------------------------------------------------
  811. /**
  812. * Get Mail Protocol
  813. *
  814. * @param bool
  815. * @return mixed
  816. */
  817. protected function _get_protocol($return = TRUE)
  818. {
  819. $this->protocol = strtolower($this->protocol);
  820. in_array($this->protocol, $this->_protocols, TRUE) OR $this->protocol = 'mail';
  821. if ($return === TRUE)
  822. {
  823. return $this->protocol;
  824. }
  825. }
  826. // --------------------------------------------------------------------
  827. /**
  828. * Get Mail Encoding
  829. *
  830. * @param bool
  831. * @return string
  832. */
  833. protected function _get_encoding($return = TRUE)
  834. {
  835. in_array($this->_encoding, $this->_bit_depths) OR $this->_encoding = '8bit';
  836. foreach ($this->_base_charsets as $charset)
  837. {
  838. if (strpos($charset, $this->charset) === 0)
  839. {
  840. $this->_encoding = '7bit';
  841. }
  842. }
  843. if ($return === TRUE)
  844. {
  845. return $this->_encoding;
  846. }
  847. }
  848. // --------------------------------------------------------------------
  849. /**
  850. * Get content type (text/html/attachment)
  851. *
  852. * @return string
  853. */
  854. protected function _get_content_type()
  855. {
  856. if ($this->mailtype === 'html')
  857. {
  858. return (count($this->_attachments) === 0) ? 'html' : 'html-attach';
  859. }
  860. elseif ($this->mailtype === 'text' && count($this->_attachments) > 0)
  861. {
  862. return 'plain-attach';
  863. }
  864. else
  865. {
  866. return 'plain';
  867. }
  868. }
  869. // --------------------------------------------------------------------
  870. /**
  871. * Set RFC 822 Date
  872. *
  873. * @return string
  874. */
  875. protected function _set_date()
  876. {
  877. $timezone = date('Z');
  878. $operator = ($timezone[0] === '-') ? '-' : '+';
  879. $timezone = abs($timezone);
  880. $timezone = floor($timezone/3600) * 100 + ($timezone % 3600) / 60;
  881. return sprintf('%s %s%04d', date('D, j M Y H:i:s'), $operator, $timezone);
  882. }
  883. // --------------------------------------------------------------------
  884. /**
  885. * Mime message
  886. *
  887. * @return string
  888. */
  889. protected function _get_mime_message()
  890. {
  891. return 'This is a multi-part message in MIME format.'.$this->newline.'Your email application may not support this format.';
  892. }
  893. // --------------------------------------------------------------------
  894. /**
  895. * Validate Email Address
  896. *
  897. * @param string
  898. * @return bool
  899. */
  900. public function validate_email($email)
  901. {
  902. if ( ! is_array($email))
  903. {
  904. $this->_set_error_message('lang:email_must_be_array');
  905. return FALSE;
  906. }
  907. foreach ($email as $val)
  908. {
  909. if ( ! $this->valid_email($val))
  910. {
  911. $this->_set_error_message('lang:email_invalid_address', $val);
  912. return FALSE;
  913. }
  914. }
  915. return TRUE;
  916. }
  917. // --------------------------------------------------------------------
  918. /**
  919. * Email Validation
  920. *
  921. * @param string
  922. * @return bool
  923. */
  924. public function valid_email($email)
  925. {
  926. if (function_exists('idn_to_ascii') && $atpos = strpos($email, '@'))
  927. {
  928. $email = substr($email, 0, ++$atpos).idn_to_ascii(substr($email, $atpos));
  929. }
  930. return (bool) filter_var($email, FILTER_VALIDATE_EMAIL);
  931. }
  932. // --------------------------------------------------------------------
  933. /**
  934. * Clean Extended Email Address: Joe Smith <joe@smith.com>
  935. *
  936. * @param string
  937. * @return string
  938. */
  939. public function clean_email($email)
  940. {
  941. if ( ! is_array($email))
  942. {
  943. return preg_match('/\<(.*)\>/', $email, $match) ? $match[1] : $email;
  944. }
  945. $clean_email = array();
  946. foreach ($email as $addy)
  947. {
  948. $clean_email[] = preg_match('/\<(.*)\>/', $addy, $match) ? $match[1] : $addy;
  949. }
  950. return $clean_email;
  951. }
  952. // --------------------------------------------------------------------
  953. /**
  954. * Build alternative plain text message
  955. *
  956. * Provides the raw message for use in plain-text headers of
  957. * HTML-formatted emails.
  958. * If the user hasn't specified his own alternative message
  959. * it creates one by stripping the HTML
  960. *
  961. * @return string
  962. */
  963. protected function _get_alt_message()
  964. {
  965. if ( ! empty($this->alt_message))
  966. {
  967. return ($this->wordwrap)
  968. ? $this->word_wrap($this->alt_message, 76)
  969. : $this->alt_message;
  970. }
  971. $body = preg_match('/\<body.*?\>(.*)\<\/body\>/si', $this->_body, $match) ? $match[1] : $this->_body;
  972. $body = str_replace("\t", '', preg_replace('#<!--(.*)--\>#', '', trim(strip_tags($body))));
  973. for ($i = 20; $i >= 3; $i--)
  974. {
  975. $body = str_replace(str_repeat("\n", $i), "\n\n", $body);
  976. }
  977. // Reduce multiple spaces
  978. $body = preg_replace('| +|', ' ', $body);
  979. return ($this->wordwrap)
  980. ? $this->word_wrap($body, 76)
  981. : $body;
  982. }
  983. // --------------------------------------------------------------------
  984. /**
  985. * Word Wrap
  986. *
  987. * @param string
  988. * @param int line-length limit
  989. * @return string
  990. */
  991. public function word_wrap($str, $charlim = NULL)
  992. {
  993. // Set the character limit, if not already present
  994. if (empty($charlim))
  995. {
  996. $charlim = empty($this->wrapchars) ? 76 : $this->wrapchars;
  997. }
  998. // Standardize newlines
  999. if (strpos($str, "\r") !== FALSE)
  1000. {
  1001. $str = str_replace(array("\r\n", "\r"), "\n", $str);
  1002. }
  1003. // Reduce multiple spaces at end of line
  1004. $str = preg_replace('| +\n|', "\n", $str);
  1005. // If the current word is surrounded by {unwrap} tags we'll
  1006. // strip the entire chunk and replace it with a marker.
  1007. $unwrap = array();
  1008. if (preg_match_all('|\{unwrap\}(.+?)\{/unwrap\}|s', $str, $matches))
  1009. {
  1010. for ($i = 0, $c = count($matches[0]); $i < $c; $i++)
  1011. {
  1012. $unwrap[] = $matches[1][$i];
  1013. $str = str_replace($matches[0][$i], '{{unwrapped'.$i.'}}', $str);
  1014. }
  1015. }
  1016. // Use PHP's native function to do the initial wordwrap.
  1017. // We set the cut flag to FALSE so that any individual words that are
  1018. // too long get left alone. In the next step we'll deal with them.
  1019. $str = wordwrap($str, $charlim, "\n", FALSE);
  1020. // Split the string into individual lines of text and cycle through them
  1021. $output = '';
  1022. foreach (explode("\n", $str) as $line)
  1023. {
  1024. // Is the line within the allowed character count?
  1025. // If so we'll join it to the output and continue
  1026. if (mb_strlen($line) <= $charlim)
  1027. {
  1028. $output .= $line.$this->newline;
  1029. continue;
  1030. }
  1031. $temp = '';
  1032. do
  1033. {
  1034. // If the over-length word is a URL we won't wrap it
  1035. if (preg_match('!\[url.+\]|://|www\.!', $line))
  1036. {
  1037. break;
  1038. }
  1039. // Trim the word down
  1040. $temp .= mb_substr($line, 0, $charlim - 1);
  1041. $line = mb_substr($line, $charlim - 1);
  1042. }
  1043. while (mb_strlen($line) > $charlim);
  1044. // If $temp contains data it means we had to split up an over-length
  1045. // word into smaller chunks so we'll add it back to our current line
  1046. if ($temp !== '')
  1047. {
  1048. $output .= $temp.$this->newline;
  1049. }
  1050. $output .= $line.$this->newline;
  1051. }
  1052. // Put our markers back
  1053. if (count($unwrap) > 0)
  1054. {
  1055. foreach ($unwrap as $key => $val)
  1056. {
  1057. $output = str_replace('{{unwrapped'.$key.'}}', $val, $output);
  1058. }
  1059. }
  1060. return $output;
  1061. }
  1062. // --------------------------------------------------------------------
  1063. /**
  1064. * Build final headers
  1065. *
  1066. * @return string
  1067. */
  1068. protected function _build_headers()
  1069. {
  1070. $this->set_header('X-Sender', $this->clean_email($this->_headers['From']));
  1071. $this->set_header('X-Mailer', $this->useragent);
  1072. $this->set_header('X-Priority', $this->_priorities[$this->priority]);
  1073. $this->set_header('Message-ID', $this->_get_message_id());
  1074. $this->set_header('Mime-Version', '1.0');
  1075. }
  1076. // --------------------------------------------------------------------
  1077. /**
  1078. * Write Headers as a string
  1079. *
  1080. * @return void
  1081. */
  1082. protected function _write_headers()
  1083. {
  1084. if ($this->protocol === 'mail')
  1085. {
  1086. if (isset($this->_headers['Subject']))
  1087. {
  1088. $this->_subject = $this->_headers['Subject'];
  1089. unset($this->_headers['Subject']);
  1090. }
  1091. }
  1092. reset($this->_headers);
  1093. $this->_header_str = '';
  1094. foreach ($this->_headers as $key => $val)
  1095. {
  1096. $val = trim($val);
  1097. if ($val !== '')
  1098. {
  1099. $this->_header_str .= $key.': '.$val.$this->newline;
  1100. }
  1101. }
  1102. if ($this->_get_protocol() === 'mail')
  1103. {
  1104. $this->_header_str = rtrim($this->_header_str);
  1105. }
  1106. }
  1107. // --------------------------------------------------------------------
  1108. /**
  1109. * Build Final Body and attachments
  1110. *
  1111. * @return bool
  1112. */
  1113. protected function _build_message()
  1114. {
  1115. if ($this->wordwrap === TRUE && $this->mailtype !== 'html')
  1116. {
  1117. $this->_body = $this->word_wrap($this->_body);
  1118. }
  1119. $this->_set_boundaries();
  1120. $this->_write_headers();
  1121. $hdr = ($this->_get_protocol() === 'mail') ? $this->newline : '';
  1122. $body = '';
  1123. switch ($this->_get_content_type())
  1124. {
  1125. case 'plain' :
  1126. $hdr .= 'Content-Type: text/plain; charset='.$this->charset.$this->newline
  1127. .'Content-Transfer-Encoding: '.$this->_get_encoding();
  1128. if ($this->_get_protocol() === 'mail')
  1129. {
  1130. $this->_header_str .= $hdr;
  1131. $this->_finalbody = $this->_body;
  1132. }
  1133. else
  1134. {
  1135. $this->_finalbody = $hdr.$this->newline.$this->newline.$this->_body;
  1136. }
  1137. return;
  1138. case 'html' :
  1139. if ($this->send_multipart === FALSE)
  1140. {
  1141. $hdr .= 'Content-Type: text/html; charset='.$this->charset.$this->newline
  1142. .'Content-Transfer-Encoding: quoted-printable';
  1143. }
  1144. else
  1145. {
  1146. $hdr .= 'Content-Type: multipart/alternative; boundary="'.$this->_alt_boundary.'"';
  1147. $body .= $this->_get_mime_message().$this->newline.$this->newline
  1148. .'--'.$this->_alt_boundary.$this->newline
  1149. .'Content-Type: text/plain; charset='.$this->charset.$this->newline
  1150. .'Content-Transfer-Encoding: '.$this->_get_encoding().$this->newline.$this->newline
  1151. .$this->_get_alt_message().$this->newline.$this->newline.'--'.$this->_alt_boundary.$this->newline
  1152. .'Content-Type: text/html; charset='.$this->charset.$this->newline
  1153. .'Content-Transfer-Encoding: quoted-printable'.$this->newline.$this->newline;
  1154. }
  1155. $this->_finalbody = $body.$this->_prep_quoted_printable($this->_body).$this->newline.$this->newline;
  1156. if ($this->_get_protocol() === 'mail')
  1157. {
  1158. $this->_header_str .= $hdr;
  1159. }
  1160. else
  1161. {
  1162. $this->_finalbody = $hdr.$this->newline.$this->newline.$this->_finalbody;
  1163. }
  1164. if ($this->send_multipart !== FALSE)
  1165. {
  1166. $this->_finalbody .= '--'.$this->_alt_boundary.'--';
  1167. }
  1168. return;
  1169. case 'plain-attach' :
  1170. $hdr .= 'Content-Type: multipart/'.$this->multipart.'; boundary="'.$this->_atc_boundary.'"';
  1171. if ($this->_get_protocol() === 'mail')
  1172. {
  1173. $this->_header_str .= $hdr;
  1174. }
  1175. $body .= $this->_get_mime_message().$this->newline
  1176. .$this->newline
  1177. .'--'.$this->_atc_boundary.$this->newline
  1178. .'Content-Type: text/plain; charset='.$this->charset.$this->newline
  1179. .'Content-Transfer-Encoding: '.$this->_get_encoding().$this->newline
  1180. .$this->newline
  1181. .$this->_body.$this->newline.$this->newline;
  1182. break;
  1183. case 'html-attach' :
  1184. $hdr .= 'Content-Type: multipart/'.$this->multipart.'; boundary="'.$this->_atc_boundary.'"';
  1185. if ($this->_get_protocol() === 'mail')
  1186. {
  1187. $this->_header_str .= $hdr;
  1188. }
  1189. $body .= $this->_get_mime_message().$this->newline.$this->newline
  1190. .'--'.$this->_atc_boundary.$this->newline
  1191. .'Content-Type: multipart/alternative; boundary="'.$this->_alt_boundary.'"'.$this->newline.$this->newline
  1192. .'--'.$this->_alt_boundary.$this->newline
  1193. .'Content-Type: text/plain; charset='.$this->charset.$this->newline
  1194. .'Content-Transfer-Encoding: '.$this->_get_encoding().$this->newline.$this->newline
  1195. .$this->_get_alt_message().$this->newline.$this->newline.'--'.$this->_alt_boundary.$this->newline
  1196. .'Content-Type: text/html; charset='.$this->charset.$this->newline
  1197. .'Content-Transfer-Encoding: quoted-printable'.$this->newline.$this->newline
  1198. .$this->_prep_quoted_printable($this->_body).$this->newline.$this->newline
  1199. .'--'.$this->_alt_boundary.'--'.$this->newline.$this->newline;
  1200. break;
  1201. }
  1202. $attachment = array();
  1203. for ($i = 0, $c = count($this->_attachments), $z = 0; $i < $c; $i++)
  1204. {
  1205. $filename = $this->_attachments[$i]['name'][0];
  1206. $basename = ($this->_attachments[$i]['name'][1] === NULL)
  1207. ? basename($filename) : $this->_attachments[$i]['name'][1];
  1208. $attachment[$z++] = '--'.$this->_atc_boundary.$this->newline
  1209. .'Content-type: '.$this->_attachments[$i]['type'].'; '
  1210. .'name="'.$basename.'"'.$this->newline
  1211. .'Content-Disposition: '.$this->_attachments[$i]['disposition'].';'.$this->newline
  1212. .'Content-Transfer-Encoding: base64'.$this->newline
  1213. .(empty($this->_attachments[$i]['cid']) ? '' : 'Content-ID: <'.$this->_attachments[$i]['cid'].'>'.$this->newline);
  1214. $attachment[$z++] = $this->_attachments[$i]['content'];
  1215. }
  1216. $body .= implode($this->newline, $attachment).$this->newline.'--'.$this->_atc_boundary.'--';
  1217. $this->_finalbody = ($this->_get_protocol() === 'mail')
  1218. ? $body
  1219. : $hdr.$this->newline.$this->newline.$body;
  1220. return TRUE;
  1221. }
  1222. // --------------------------------------------------------------------
  1223. /**
  1224. * Prep Quoted Printable
  1225. *
  1226. * Prepares string for Quoted-Printable Content-Transfer-Encoding
  1227. * Refer to RFC 2045 http://www.ietf.org/rfc/rfc2045.txt
  1228. *
  1229. * @param string
  1230. * @return string
  1231. */
  1232. protected function _prep_quoted_printable($str)
  1233. {
  1234. // We are intentionally wrapping so mail servers will encode characters
  1235. // properly and MUAs will behave, so {unwrap} must go!
  1236. $str = str_replace(array('{unwrap}', '{/unwrap}'), '', $str);
  1237. // RFC 2045 specifies CRLF as "\r\n".
  1238. // However, many developers choose to override that and violate
  1239. // the RFC rules due to (apparently) a bug in MS Exchange,
  1240. // which only works with "\n".
  1241. if ($this->crlf === "\r\n")
  1242. {
  1243. if (is_php('5.3'))
  1244. {
  1245. return quoted_printable_encode($str);
  1246. }
  1247. elseif (function_exists('imap_8bit'))
  1248. {
  1249. return imap_8bit($str);
  1250. }
  1251. }
  1252. // Reduce multiple spaces & remove nulls
  1253. $str = preg_replace(array('| +|', '/\x00+/'), array(' ', ''), $str);
  1254. // Standardize newlines
  1255. if (strpos($str, "\r") !== FALSE)
  1256. {
  1257. $str = str_replace(array("\r\n", "\r"), "\n", $str);
  1258. }
  1259. $escape = '=';
  1260. $output = '';
  1261. foreach (explode("\n", $str) as $line)
  1262. {
  1263. $length = strlen($line);
  1264. $temp = '';
  1265. // Loop through each character in the line to add soft-wrap
  1266. // characters at the end of a line " =\r\n" and add the newly
  1267. // processed line(s) to the output (see comment on $crlf class property)
  1268. for ($i = 0; $i < $length; $i++)
  1269. {
  1270. // Grab the next character
  1271. $char = $line[$i];
  1272. $ascii = ord($char);
  1273. // Convert spaces and tabs but only if it's the end of the line
  1274. if ($i === ($length - 1) && ($ascii === 32 OR $ascii === 9))
  1275. {
  1276. $char = $escape.sprintf('%02s', dechex($ascii));
  1277. }
  1278. elseif ($ascii === 61) // encode = signs
  1279. {
  1280. $char = $escape.strtoupper(sprintf('%02s', dechex($ascii))); // =3D
  1281. }
  1282. // If we're at the character limit, add the line to the output,
  1283. // reset our temp variable, and keep on chuggin'
  1284. if ((strlen($temp) + strlen($char)) >= 76)
  1285. {
  1286. $output .= $temp.$escape.$this->crlf;
  1287. $temp = '';
  1288. }
  1289. // Add the character to our temporary line
  1290. $temp .= $char;
  1291. }
  1292. // Add our completed line to the output
  1293. $output .= $temp.$this->crlf;
  1294. }
  1295. // get rid of extra CRLF tacked onto the end
  1296. return substr($output, 0, strlen($this->crlf) * -1);
  1297. }
  1298. // --------------------------------------------------------------------
  1299. /**
  1300. * Prep Q Encoding
  1301. *
  1302. * Performs "Q Encoding" on a string for use in email headers.
  1303. * It's related but not identical to quoted-printable, so it has its
  1304. * own method.
  1305. *
  1306. * @param string
  1307. * @return string
  1308. */
  1309. protected function _prep_q_encoding($str)
  1310. {
  1311. $str = str_replace(array("\r", "\n"), '', $str);
  1312. if ($this->charset === 'UTF-8')
  1313. {
  1314. if (MB_ENABLED === TRUE)
  1315. {
  1316. return mb_encode_mimeheader($str, $this->charset, 'Q', $this->crlf);
  1317. }
  1318. elseif (ICONV_ENABLED === TRUE)
  1319. {
  1320. $output = @iconv_mime_encode('', $str,
  1321. array(
  1322. 'scheme' => 'Q',
  1323. 'line-length' => 76,
  1324. 'input-charset' => $this->charset,
  1325. 'output-charset' => $this->charset,
  1326. 'line-break-chars' => $this->crlf
  1327. )
  1328. );
  1329. // There are reports that iconv_mime_encode() might fail and return FALSE
  1330. if ($output !== FALSE)
  1331. {
  1332. // iconv_mime_encode() will always put a header field name.
  1333. // We've passed it an empty one, but it still prepends our
  1334. // encoded string with ': ', so we need to strip it.
  1335. return substr($output, 2);
  1336. }
  1337. $chars = iconv_strlen($str, 'UTF-8');
  1338. }
  1339. }
  1340. // We might already have this set for UTF-8
  1341. isset($chars) OR $chars = strlen($str);
  1342. $output = '=?'.$this->charset.'?Q?';
  1343. for ($i = 0, $length = strlen($output); $i < $chars; $i++)
  1344. {
  1345. $chr = ($this->charset === 'UTF-8' && ICONV_ENABLED === TRUE)
  1346. ? '='.implode('=', str_split(strtoupper(bin2hex(iconv_substr($str, $i, 1, $this->charset))), 2))
  1347. : '='.strtoupper(bin2hex($str[$i]));
  1348. // RFC 2045 sets a limit of 76 characters per line.
  1349. // We'll append ?= to the end of each line though.
  1350. if ($length + ($l = strlen($chr)) > 74)
  1351. {
  1352. $output .= '?='.$this->crlf // EOL
  1353. .' =?'.$this->charset.'?Q?'.$chr; // New line
  1354. $length = 6 + strlen($this->charset) + $l; // Reset the length for the new line
  1355. }
  1356. else
  1357. {
  1358. $output .= $chr;
  1359. $length += $l;
  1360. }
  1361. }
  1362. // End the header
  1363. return $output.'?=';
  1364. }
  1365. // --------------------------------------------------------------------
  1366. /**
  1367. * Send Email
  1368. *
  1369. * @param bool $auto_clear = TRUE
  1370. * @return bool
  1371. */
  1372. public function send($auto_clear = TRUE)
  1373. {
  1374. if ($this->_replyto_flag === FALSE)
  1375. {
  1376. $this->reply_to($this->_headers['From']);
  1377. }
  1378. if ( ! isset($this->_recipients) && ! isset($this->_headers['To'])
  1379. && ! isset($this->_bcc_array) && ! isset($this->_headers['Bcc'])
  1380. && ! isset($this->_headers['Cc']))
  1381. {
  1382. $this->_set_error_message('lang:email_no_recipients');
  1383. return FALSE;
  1384. }
  1385. $this->_build_headers();
  1386. if ($this->bcc_batch_mode && count($this->_bcc_array) > $this->bcc_batch_size)
  1387. {
  1388. $result = $this->batch_bcc_send();
  1389. if ($result && $auto_clear)
  1390. {
  1391. $this->clear();
  1392. }
  1393. return $result;
  1394. }
  1395. if ($this->_build_message() === FALSE)
  1396. {
  1397. return FALSE;
  1398. }
  1399. $result = $this->_spool_email();
  1400. if ($result && $auto_clear)
  1401. {
  1402. $this->clear();
  1403. }
  1404. return $result;
  1405. }
  1406. // --------------------------------------------------------------------
  1407. /**
  1408. * Batch Bcc Send. Sends groups of BCCs in batches
  1409. *
  1410. * @return void
  1411. */
  1412. public function batch_bcc_send()
  1413. {
  1414. $float = $this->bcc_batch_size - 1;
  1415. $set = '';
  1416. $chunk = array();
  1417. for ($i = 0, $c = count($this->_bcc_array); $i < $c; $i++)
  1418. {
  1419. if (isset($this->_bcc_array[$i]))
  1420. {
  1421. $set .= ', '.$this->_bcc_array[$i];
  1422. }
  1423. if ($i === $float)
  1424. {
  1425. $chunk[] = substr($set, 1);
  1426. $float += $this->bcc_batch_size;
  1427. $set = '';
  1428. }
  1429. if ($i === $c-1)
  1430. {
  1431. $chunk[] = substr($set, 1);
  1432. }
  1433. }
  1434. for ($i = 0, $c = count($chunk); $i < $c; $i++)
  1435. {
  1436. unset($this->_headers['Bcc']);
  1437. $bcc = $this->clean_email($this->_str_to_array($chunk[$i]));
  1438. if ($this->protocol !== 'smtp')
  1439. {
  1440. $this->set_header('Bcc', implode(', ', $bcc));
  1441. }
  1442. else
  1443. {
  1444. $this->_bcc_array = $bcc;
  1445. }
  1446. if ($this->_build_message() === FALSE)
  1447. {
  1448. return FALSE;
  1449. }
  1450. $this->_spool_email();
  1451. }
  1452. }
  1453. // --------------------------------------------------------------------
  1454. /**
  1455. * Unwrap special elements
  1456. *
  1457. * @return void
  1458. */
  1459. protected function _unwrap_specials()
  1460. {
  1461. $this->_finalbody = preg_replace_callback('/\{unwrap\}(.*?)\{\/unwrap\}/si', array($this, '_remove_nl_callback'), $this->_finalbody);
  1462. }
  1463. // --------------------------------------------------------------------
  1464. /**
  1465. * Strip line-breaks via callback
  1466. *
  1467. * @param string $matches
  1468. * @return string
  1469. */
  1470. protected function _remove_nl_callback($matches)
  1471. {
  1472. if (strpos($matches[1], "\r") !== FALSE OR strpos($matches[1], "\n") !== FALSE)
  1473. {
  1474. $matches[1] = str_replace(array("\r\n", "\r", "\n"), '', $matches[1]);
  1475. }
  1476. return $matches[1];
  1477. }
  1478. // --------------------------------------------------------------------
  1479. /**
  1480. * Spool mail to the mail server
  1481. *
  1482. * @return bool
  1483. */
  1484. protected function _spool_email()
  1485. {
  1486. $this->_unwrap_specials();
  1487. $method = '_send_with_'.$this->_get_protocol();
  1488. if ( ! $this->$method())
  1489. {
  1490. $this->_set_error_message('lang:email_send_failure_'.($this->_get_protocol() === 'mail' ? 'phpmail' : $this->_get_protocol()));
  1491. return FALSE;
  1492. }
  1493. $this->_set_error_message('lang:email_sent', $this->_get_protocol());
  1494. return TRUE;
  1495. }
  1496. // --------------------------------------------------------------------
  1497. /**
  1498. * Send using mail()
  1499. *
  1500. * @return bool
  1501. */
  1502. protected function _send_with_mail()
  1503. {
  1504. if (is_array($this->_recipients))
  1505. {
  1506. $this->_recipients = implode(', ', $this->_recipients);
  1507. }
  1508. if ($this->_safe_mode === TRUE)
  1509. {
  1510. return mail($this->_recipients, $this->_subject, $this->_finalbody, $this->_header_str);
  1511. }
  1512. else
  1513. {
  1514. // most documentation of sendmail using the "-f" flag lacks a space after it, however
  1515. // we've encountered servers that seem to require it to be in place.
  1516. return mail($this->_recipients, $this->_subject, $this->_finalbody, $this->_header_str, '-f '.$this->clean_email($this->_headers['Return-Path']));
  1517. }
  1518. }
  1519. // --------------------------------------------------------------------
  1520. /**
  1521. * Send using Sendmail
  1522. *
  1523. * @return bool
  1524. */
  1525. protected function _send_with_sendmail()
  1526. {
  1527. // is popen() enabled?
  1528. if ( ! function_usable('popen')
  1529. OR FALSE === ($fp = @popen(
  1530. $this->mailpath.' -oi -f '.$this->clean_email($this->_headers['From'])
  1531. .' -t -r '.$this->clean_email($this->_headers['Return-Path'])
  1532. , 'w'))
  1533. ) // server probably has popen disabled, so nothing we can do to get a verbose error.
  1534. {
  1535. return FALSE;
  1536. }
  1537. fputs($fp, $this->_header_str);
  1538. fputs($fp, $this->_finalbody);
  1539. $status = pclose($fp);
  1540. if ($status !== 0)
  1541. {
  1542. $this->_set_error_message('lang:email_exit_status', $status);
  1543. $this->_set_error_message('lang:email_no_socket');
  1544. return FALSE;
  1545. }
  1546. return TRUE;
  1547. }
  1548. // --------------------------------------------------------------------
  1549. /**
  1550. * Send using SMTP
  1551. *
  1552. * @return bool
  1553. */
  1554. protected function _send_with_smtp()
  1555. {
  1556. if ($this->smtp_host === '')
  1557. {
  1558. $this->_set_error_message('lang:email_no_hostname');
  1559. return FALSE;
  1560. }
  1561. if ( ! $this->_smtp_connect() OR ! $this->_smtp_authenticate())
  1562. {
  1563. return FALSE;
  1564. }
  1565. $this->_send_command('from', $this->clean_email($this->_headers['From']));
  1566. foreach ($this->_recipients as $val)
  1567. {
  1568. $this->_send_command('to', $val);
  1569. }
  1570. if (count($this->_cc_array) > 0)
  1571. {
  1572. foreach ($this->_cc_array as $val)
  1573. {
  1574. if ($val !== '')
  1575. {
  1576. $this->_send_command('to', $val);
  1577. }
  1578. }
  1579. }
  1580. if (count($this->_bcc_array) > 0)
  1581. {
  1582. foreach ($this->_bcc_array as $val)
  1583. {
  1584. if ($val !== '')
  1585. {
  1586. $this->_send_command('to', $val);
  1587. }
  1588. }
  1589. }
  1590. $this->_send_command('data');
  1591. // perform dot transformation on any lines that begin with a dot
  1592. $this->_send_data($this->_header_str.preg_replace('/^\./m', '..$1', $this->_finalbody));
  1593. $this->_send_data('.');
  1594. $reply = $this->_get_smtp_data();
  1595. $this->_set_error_message($reply);
  1596. if (strpos($reply, '250') !== 0)
  1597. {
  1598. $this->_set_error_message('lang:email_smtp_error', $reply);
  1599. return FALSE;
  1600. }
  1601. if ($this->smtp_keepalive)
  1602. {
  1603. $this->_send_command('reset');
  1604. }
  1605. else
  1606. {
  1607. $this->_send_command('quit');
  1608. }
  1609. return TRUE;
  1610. }
  1611. // --------------------------------------------------------------------
  1612. /**
  1613. * SMTP Connect
  1614. *
  1615. * @return string
  1616. */
  1617. protected function _smtp_connect()
  1618. {
  1619. if (is_resource($this->_smtp_connect))
  1620. {
  1621. return TRUE;
  1622. }
  1623. $ssl = ($this->smtp_crypto === 'ssl') ? 'ssl://' : '';
  1624. $this->_smtp_connect = fsockopen($ssl.$this->smtp_host,
  1625. $this->smtp_port,
  1626. $errno,
  1627. $errstr,
  1628. $this->smtp_timeout);
  1629. if ( ! is_resource($this->_smtp_connect))
  1630. {
  1631. $this->_set_error_message('lang:email_smtp_error', $errno.' '.$errstr);
  1632. return FALSE;
  1633. }
  1634. stream_set_timeout($this->_smtp_connect, $this->smtp_timeout);
  1635. $this->_set_error_message($this->_get_smtp_data());
  1636. if ($this->smtp_crypto === 'tls')
  1637. {
  1638. $this->_send_command('hello');
  1639. $this->_send_command('starttls');
  1640. $crypto = stream_socket_enable_crypto($this->_smtp_connect, TRUE, STREAM_CRYPTO_METHOD_TLS_CLIENT);
  1641. if ($crypto !== TRUE)
  1642. {
  1643. $this->_set_error_message('lang:email_smtp_error', $this->_get_smtp_data());
  1644. return FALSE;
  1645. }
  1646. }
  1647. return $this->_send_command('hello');
  1648. }
  1649. // --------------------------------------------------------------------
  1650. /**
  1651. * Send SMTP command
  1652. *
  1653. * @param string
  1654. * @param string
  1655. * @return string
  1656. */
  1657. protected function _send_command($cmd, $data = '')
  1658. {
  1659. switch ($cmd)
  1660. {
  1661. case 'hello' :
  1662. if ($this->_smtp_auth OR $this->_get_encoding() === '8bit')
  1663. {
  1664. $this->_send_data('EHLO '.$this->_get_hostname());
  1665. }
  1666. else
  1667. {
  1668. $this->_send_data('HELO '.$this->_get_hostname());
  1669. }
  1670. $resp = 250;
  1671. break;
  1672. case 'starttls' :
  1673. $this->_send_data('STARTTLS');
  1674. $resp = 220;
  1675. break;
  1676. case 'from' :
  1677. $this->_send_data('MAIL FROM:<'.$data.'>');
  1678. $resp = 250;
  1679. break;
  1680. case 'to' :
  1681. if ($this->dsn)
  1682. {
  1683. $this->_send_data('RCPT TO:<'.$data.'> NOTIFY=SUCCESS,DELAY,FAILURE ORCPT=rfc822;'.$data);
  1684. }
  1685. else
  1686. {
  1687. $this->_send_data('RCPT TO:<'.$data.'>');
  1688. }
  1689. $resp = 250;
  1690. break;
  1691. case 'data' :
  1692. $this->_send_data('DATA');
  1693. $resp = 354;
  1694. break;
  1695. case 'reset':
  1696. $this->_send_data('RSET');
  1697. $resp = 250;
  1698. break;
  1699. case 'quit' :
  1700. $this->_send_data('QUIT');
  1701. $resp = 221;
  1702. break;
  1703. }
  1704. $reply = $this->_get_smtp_data();
  1705. $this->_debug_msg[] = '<pre>'.$cmd.': '.$reply.'</pre>';
  1706. if ((int) substr($reply, 0, 3) !== $resp)
  1707. {
  1708. $this->_set_error_message('lang:email_smtp_error', $reply);
  1709. return FALSE;
  1710. }
  1711. if ($cmd === 'quit')
  1712. {
  1713. fclose($this->_smtp_connect);
  1714. }
  1715. return TRUE;
  1716. }
  1717. // --------------------------------------------------------------------
  1718. /**
  1719. * SMTP Authenticate
  1720. *
  1721. * @return bool
  1722. */
  1723. protected function _smtp_authenticate()
  1724. {
  1725. if ( ! $this->_smtp_auth)
  1726. {
  1727. return TRUE;
  1728. }
  1729. if ($this->smtp_user === '' && $this->smtp_pass === '')
  1730. {
  1731. $this->_set_error_message('lang:email_no_smtp_unpw');
  1732. return FALSE;
  1733. }
  1734. $this->_send_data('AUTH LOGIN');
  1735. $reply = $this->_get_smtp_data();
  1736. if (strpos($reply, '503') === 0) // Already authenticated
  1737. {
  1738. return TRUE;
  1739. }
  1740. elseif (strpos($reply, '334') !== 0)
  1741. {
  1742. $this->_set_error_message('lang:email_failed_smtp_login', $reply);
  1743. return FALSE;
  1744. }
  1745. $this->_send_data(base64_encode($this->smtp_user));
  1746. $reply = $this->_get_smtp_data();
  1747. if (strpos($reply, '334') !== 0)
  1748. {
  1749. $this->_set_error_message('lang:email_smtp_auth_un', $reply);
  1750. return FALSE;
  1751. }
  1752. $this->_send_data(base64_encode($this->smtp_pass));
  1753. $reply = $this->_get_smtp_data();
  1754. if (strpos($reply, '235') !== 0)
  1755. {
  1756. $this->_set_error_message('lang:email_smtp_auth_pw', $reply);
  1757. return FALSE;
  1758. }
  1759. return TRUE;
  1760. }
  1761. // --------------------------------------------------------------------
  1762. /**
  1763. * Send SMTP data
  1764. *
  1765. * @param string $data
  1766. * @return bool
  1767. */
  1768. protected function _send_data($data)
  1769. {
  1770. $data .= $this->newline;
  1771. for ($written = 0, $length = strlen($data); $written < $length; $written += $result)
  1772. {
  1773. if (($result = fwrite($this->_smtp_connect, substr($data, $written))) === FALSE)
  1774. {
  1775. break;
  1776. }
  1777. }
  1778. if ($result === FALSE)
  1779. {
  1780. $this->_set_error_message('lang:email_smtp_data_failure', $data);
  1781. return FALSE;
  1782. }
  1783. return TRUE;
  1784. }
  1785. // --------------------------------------------------------------------
  1786. /**
  1787. * Get SMTP data
  1788. *
  1789. * @return string
  1790. */
  1791. protected function _get_smtp_data()
  1792. {
  1793. $data = '';
  1794. while ($str = fgets($this->_smtp_connect, 512))
  1795. {
  1796. $data .= $str;
  1797. if ($str[3] === ' ')
  1798. {
  1799. break;
  1800. }
  1801. }
  1802. return $data;
  1803. }
  1804. // --------------------------------------------------------------------
  1805. /**
  1806. * Get Hostname
  1807. *
  1808. * @return string
  1809. */
  1810. protected function _get_hostname()
  1811. {
  1812. return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost.localdomain';
  1813. }
  1814. // --------------------------------------------------------------------
  1815. /**
  1816. * Get Debug Message
  1817. *
  1818. * @param array $include List of raw data chunks to include in the output
  1819. * Valid options are: 'headers', 'subject', 'body'
  1820. * @return string
  1821. */
  1822. public function print_debugger($include = array('headers', 'subject', 'body'))
  1823. {
  1824. $msg = '';
  1825. if (count($this->_debug_msg) > 0)
  1826. {
  1827. foreach ($this->_debug_msg as $val)
  1828. {
  1829. $msg .= $val;
  1830. }
  1831. }
  1832. // Determine which parts of our raw data needs to be printed
  1833. $raw_data = '';
  1834. is_array($include) OR $include = array($include);
  1835. if (in_array('headers', $include, TRUE))
  1836. {
  1837. $raw_data = htmlspecialchars($this->_header_str)."\n";
  1838. }
  1839. if (in_array('subject', $include, TRUE))
  1840. {
  1841. $raw_data .= htmlspecialchars($this->_subject)."\n";
  1842. }
  1843. if (in_array('body', $include, TRUE))
  1844. {
  1845. $raw_data .= htmlspecialchars($this->_finalbody);
  1846. }
  1847. return $msg.($raw_data === '' ? '' : '<pre>'.$raw_data.'</pre>');
  1848. }
  1849. // --------------------------------------------------------------------
  1850. /**
  1851. * Set Message
  1852. *
  1853. * @param string $msg
  1854. * @param string $val = ''
  1855. * @return void
  1856. */
  1857. protected function _set_error_message($msg, $val = '')
  1858. {
  1859. $CI =& get_instance();
  1860. $CI->lang->load('email');
  1861. if (sscanf($msg, 'lang:%s', $line) !== 1 OR FALSE === ($line = $CI->lang->line($line)))
  1862. {
  1863. $this->_debug_msg[] = str_replace('%s', $val, $msg).'<br />';
  1864. }
  1865. else
  1866. {
  1867. $this->_debug_msg[] = str_replace('%s', $val, $line).'<br />';
  1868. }
  1869. }
  1870. // --------------------------------------------------------------------
  1871. /**
  1872. * Mime Types
  1873. *
  1874. * @param string
  1875. * @return string
  1876. */
  1877. protected function _mime_types($ext = '')
  1878. {
  1879. $ext = strtolower($ext);
  1880. $mimes =& get_mimes();
  1881. if (isset($mimes[$ext]))
  1882. {
  1883. return is_array($mimes[$ext])
  1884. ? current($mimes[$ext])
  1885. : $mimes[$ext];
  1886. }
  1887. return 'application/x-unknown-content-type';
  1888. }
  1889. }
  1890. /* End of file Email.php */
  1891. /* Location: ./system/libraries/Email.php */