PageRenderTime 56ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/library/swift/lib/Swift.php

https://github.com/fb83/Project-Pier
PHP | 1702 lines | 911 code | 61 blank | 730 comment | 154 complexity | 0349c9008deba82e8b4a83b05c2faa73 MD5 | raw file
Possible License(s): AGPL-1.0, GPL-2.0, AGPL-3.0, LGPL-2.1, GPL-3.0
  1. <?php
  2. /**
  3. * Swift Mailer: A Flexible PHP Mailer Class.
  4. *
  5. * Current functionality:
  6. *
  7. * * Send uses one single connection to the SMTP server
  8. * * Doesn't rely on mail()
  9. * * Custom Headers
  10. * * Unlimited redundant connections (can be mixed type)
  11. * * Connection cycling & load balancing
  12. * * Sends Multipart messages, handles encoding
  13. * * Sends Plain-text single-part emails
  14. * * Fast Cc and Bcc handling
  15. * * Immune to rejected recipients (sends to subsequent recipients w/out error)
  16. * * Set Priority Level
  17. * * Request Read Receipts
  18. * * Unicode UTF-8 support with auto-detection
  19. * * Auto-detection of SMTP/Sendmail details based on PHP & server configuration
  20. * * Batch emailing with multiple To's or without
  21. * * Support for multiple attachments
  22. * * Sendmail (or other binary) support
  23. * * Pluggable SMTP Authentication (LOGIN, PLAIN, MD5-CRAM, POP Before SMTP)
  24. * * Secure Socket Layer connections (SSL)
  25. * * Transport Layer security (TLS) - Gmail account holders!
  26. * * Send mail with inline embedded images easily (or embed other file types)!
  27. * * Loadable plugin support with event handling features
  28. *
  29. * @package Swift
  30. * @version 2.1.17
  31. * @author Chris Corbyn
  32. * @date 18th October 2006
  33. * @license http://www.gnu.org/licenses/lgpl.txt Lesser GNU Public License
  34. *
  35. * @copyright Copyright &copy; 2006 Chris Corbyn - All Rights Reserved.
  36. * @filesource
  37. *
  38. * -----------------------------------------------------------------------
  39. *
  40. * This library is free software; you can redistribute it and/or
  41. * modify it under the terms of the GNU Lesser General Public
  42. * License as published by the Free Software Foundation; either
  43. * version 2.1 of the License, or any later version.
  44. *
  45. * This library is distributed in the hope that it will be useful,
  46. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  47. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  48. * Lesser General Public License for more details.
  49. *
  50. * You should have received a copy of the GNU Lesser General Public
  51. * License along with this library; if not, write to
  52. *
  53. * The Free Software Foundation, Inc.,
  54. * 51 Franklin Street,
  55. * Fifth Floor,
  56. * Boston,
  57. * MA 02110-1301 USA
  58. *
  59. * "Chris Corbyn" <chris@w3style.co.uk>
  60. *
  61. */
  62. if (!defined('SWIFT_VERSION')) define('SWIFT_VERSION', '2.1.17');
  63. /**
  64. * Swift Plugin Interface. Describes the methods which plugins should implement
  65. * @package Swift
  66. */
  67. interface Swift_IPlugin
  68. {
  69. /**
  70. * Required Properties
  71. *
  72. * private SwiftInstance;
  73. * public pluginName;
  74. */
  75. /**
  76. * Loads an instance of Swift to the Plugin
  77. * @param object SwiftInstance
  78. * @return void
  79. */
  80. public function loadBaseObject(&$object);
  81. /**
  82. * Optional Methods to implement
  83. *
  84. * public function onLoad();
  85. * public function onClose();
  86. * public function onFail();
  87. * public function onError();
  88. * public function onBeforeSend();
  89. * public function onSend();
  90. * public function onBeforeCommand();
  91. * public function onCommand();
  92. * public function onLog();
  93. * public function onAuthenticate();
  94. * public function onFlush();
  95. * public function onResponse();
  96. */
  97. }
  98. /**
  99. * Swift Authenticator Interface. Describes the methods which authenticators should implement
  100. * @package Swift
  101. */
  102. interface Swift_IAuthenticator
  103. {
  104. /**
  105. * Required Properties
  106. * private SwiftInstance;
  107. * public serverString;
  108. */
  109. /**
  110. * Loads an instance of Swift to the Plugin
  111. * @param object SwiftInstance
  112. * @return void
  113. */
  114. public function loadBaseObject(&$object);
  115. /**
  116. * Executes the logic in the authentication mechanism
  117. * @param string username
  118. * @param string password
  119. * @return bool successful
  120. */
  121. public function run($username, $password);
  122. }
  123. /**
  124. * Swift Connection Handler Interface.
  125. * Describes the methods which connection handlers should implement
  126. * @package Swift
  127. */
  128. interface Swift_IConnection
  129. {
  130. /**
  131. * Required properties
  132. *
  133. * public readHook;
  134. * public writeHook;
  135. * public error
  136. */
  137. /**
  138. * Establishes a connection with the MTA
  139. * @return bool connected
  140. */
  141. public function start();
  142. /**
  143. * Closes the connection with the MTA
  144. * @return void
  145. */
  146. public function stop();
  147. /**
  148. * Returns a boolean value TRUE if the connection is active.
  149. * @return bool connected
  150. */
  151. public function isConnected();
  152. }
  153. /**
  154. * Swift Mailer Class.
  155. * Accepts connections to an MTA and deals with the sending and processing of
  156. * commands and responses.
  157. * @package Swift
  158. */
  159. class Swift
  160. {
  161. /**
  162. * Plugins container
  163. * @var array plugins
  164. * @private
  165. */
  166. private $plugins = array();
  167. private $esmtp = false;
  168. private $_8bitmime = false;
  169. private $autoCompliance = false;
  170. /**
  171. * Whether or not Swift should send unique emails to all "To"
  172. * recipients or just bulk them together in the To header.
  173. * @var bool use_exact
  174. */
  175. private $useExactCopy = false;
  176. private $domain = 'SwiftUser';
  177. private $mimeBoundary;
  178. private $mimeWarning;
  179. /**
  180. * MIME Parts container
  181. * @var array parts
  182. * @private
  183. */
  184. private $parts = array();
  185. /**
  186. * Attachment data container
  187. * @var array attachments
  188. * @private
  189. */
  190. private $attachments = array();
  191. /**
  192. * Inline image container
  193. * @var array image parts
  194. * @private
  195. */
  196. private $images = array();
  197. /**
  198. * Response codes expected for commands
  199. * $command => $code
  200. * @var array codes
  201. * @private
  202. */
  203. private $expectedCodes = array(
  204. 'ehlo' => 250,
  205. 'helo' => 250,
  206. 'mail' => 250,
  207. 'rcpt' => 250,
  208. 'data' => 354
  209. );
  210. /**
  211. * Blind-carbon-copy address container
  212. * @var array addresses
  213. */
  214. private $Bcc = array();
  215. /**
  216. * Carbon-copy address container
  217. * @var array addresses
  218. */
  219. private $Cc = array();
  220. /**
  221. * The address any replies will go to
  222. * @var string address
  223. */
  224. private $replyTo;
  225. /**
  226. * The addresses we're sending to
  227. * @var string address
  228. */
  229. private $to = array();
  230. /**
  231. * The sender of the email
  232. * @var string sender
  233. */
  234. private $from;
  235. /**
  236. * Priority value 1 (high) to 5 (low)
  237. * @var int priority (1-5)
  238. */
  239. private $priority = 3;
  240. /**
  241. * Whether a read-receipt is required
  242. * @var bool read receipt
  243. */
  244. private $readReceipt = false;
  245. /**
  246. * The max number of entires that can exist in the log
  247. * (saves memory)
  248. * @var int log size
  249. */
  250. private $maxLogSize = 30;
  251. /**
  252. * The address to which bounces are sent
  253. * @var string Return-Path:
  254. */
  255. private $returnPath;
  256. /**
  257. * Connection object (container holding a socket)
  258. * @var object connection
  259. */
  260. public $connection;
  261. /**
  262. * Authenticators container
  263. * @var array authenticators
  264. */
  265. public $authenticators = array();
  266. public $authTypes = array();
  267. /**
  268. * Holds the username used in authentication (if any)
  269. * @var string username
  270. */
  271. public $username;
  272. /**
  273. * Holds the password used in authentication (if any)
  274. * @var string password
  275. */
  276. public $password;
  277. public $charset = "ISO-8859-1";
  278. private $userCharset = false;
  279. /**
  280. * Boolean value representing if Swift has failed or not
  281. * @var bool failed
  282. */
  283. public $failed = false;
  284. /**
  285. * If Swift should clear headers etc automatically
  286. * @var bool autoFlush
  287. */
  288. public $autoFlush = true;
  289. /**
  290. * Numeric code from the last MTA response
  291. * @var int code
  292. */
  293. public $responseCode;
  294. /**
  295. * Keyword of the command being sent
  296. * @var string keyword
  297. */
  298. public $commandKeyword;
  299. /**
  300. * Last email sent or email about to be sent (dependant on location)
  301. * @var array commands
  302. */
  303. public $currentMail = array();
  304. /**
  305. * Email headers
  306. * @var string headers
  307. */
  308. public $headers;
  309. public $currentCommand = '';
  310. /**
  311. * Errors container
  312. * @var array errors
  313. */
  314. public $errors = array();
  315. /**
  316. * Log container
  317. * @var array transactions
  318. */
  319. public $transactions = array();
  320. public $lastTransaction;
  321. public $lastError;
  322. /**
  323. * The very most recent response received from the MTA
  324. * @var string response
  325. */
  326. public $lastResponse;
  327. /**
  328. * The total number of failed recipients
  329. * @var int failed
  330. */
  331. private $failCount = 0;
  332. /**
  333. * Number of failed recipients for this email
  334. * @var int failed
  335. */
  336. private $subFailCount = 0;
  337. /**
  338. * Number of addresses expected to pass this email
  339. * @var int recipients
  340. */
  341. private $numAddresses;
  342. /**
  343. * Container for any recipients rejected
  344. * @var array failed addresses
  345. */
  346. private $failedAddresses = array();
  347. /**
  348. * Number of commands which will be skipped
  349. */
  350. public $ignoreCommands = 0;
  351. /**
  352. * Number of commands skipped thus far
  353. */
  354. private $skippedCommands = 0;
  355. /**
  356. * The encoding mode to use in headers (default base64)
  357. * @var char mode
  358. */
  359. private $headerEncoding = 'B';
  360. /**
  361. * Swift Constructor
  362. * @param object Swift_IConnection
  363. * @param string user_domain, optional
  364. */
  365. public function __construct(Swift_IConnection &$object, $domain=false)
  366. {
  367. if (!$domain) $domain = !empty($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'SwiftUser';
  368. $this->domain = $domain;
  369. $this->connection =& $object;
  370. $this->connect();
  371. // * Hey this library is FREE so it's not much to ask ;) But if you really do want to
  372. // remove this header then go ahead of course... what's GPL for? :P
  373. $this->headers = "X-Mailer: Swift ".SWIFT_VERSION." by Chris Corbyn\r\n";
  374. $this->mimeWarning = "This part of the E-mail should never be seen. If\r\n".
  375. "you are reading this, consider upgrading your e-mail\r\n".
  376. "client to a MIME-compatible client.";
  377. }
  378. /**
  379. * Connect to the server
  380. * @return bool connected
  381. */
  382. public function connect()
  383. {
  384. if (!$this->connection->start())
  385. {
  386. $this->fail();
  387. $error = 'Connection to the given MTA failed.';
  388. if (!empty($this->connection->error)) $error .= ' The Connection Interface said: '.$this->connection->error;
  389. $this->logError($error, 0);
  390. return false;
  391. }
  392. else
  393. {
  394. $this->handshake();
  395. return true;
  396. }
  397. }
  398. /**
  399. * Returns TRUE if the connection is active.
  400. */
  401. public function isConnected()
  402. {
  403. return $this->connection->isConnected();
  404. }
  405. /**
  406. * Sends the standard polite greetings to the MTA and then
  407. * identifies the MTA's capabilities
  408. */
  409. public function handshake()
  410. {
  411. $this->commandKeyword = "";
  412. //What did the server greet us with on connect?
  413. $this->logTransaction();
  414. if ($this->supportsESMTP($this->lastResponse))
  415. {
  416. //Just being polite
  417. $list = $this->command("EHLO {$this->domain}\r\n");
  418. $this->check8BitMime($this->lastResponse);
  419. $this->getAuthenticationMethods($list);
  420. $this->esmtp = true;
  421. }
  422. else $this->command("HELO {$this->domain}\r\n");
  423. }
  424. /**
  425. * Check if the server allows 8bit emails to be sent without quoted-printable encoding
  426. * @param string EHLO response
  427. */
  428. private function check8BitMime($string)
  429. {
  430. if (strpos($string, '8BITMIME')) $this->_8bitmime = true;
  431. }
  432. /**
  433. * Checks for Extended SMTP support
  434. * @param string MTA greeting
  435. * @return bool ESMTP
  436. * @private
  437. */
  438. private function supportsESMTP($greeting)
  439. {
  440. //Not mentiioned in RFC 2821 but this how it's done
  441. if (strpos($greeting, 'ESMTP')) return true;
  442. else return false;
  443. }
  444. /**
  445. * Set the maximum num ber of entries in the log
  446. * @param int size
  447. */
  448. public function setMaxLogSize($size)
  449. {
  450. $this->maxLogSize = (int) $size;
  451. }
  452. /**
  453. * Sets the priority level of the email
  454. * This must be 1 to 5 where 1 is highest
  455. * @param int priority
  456. */
  457. public function setPriority($level)
  458. {
  459. $level = (int) $level;
  460. if ($level < 1) $level = 1;
  461. if ($level > 5) $level = 5;
  462. switch ($level)
  463. {
  464. case 1: case 2:
  465. $this->addHeaders("X-Priority: $level\r\nX-MSMail-Priority: High");
  466. break;
  467. case 4: case 5:
  468. $this->addHeaders("X-Priority: $level\r\nX-MSMail-Priority: Low");
  469. break;
  470. case 3: default:
  471. $this->addHeaders("X-Priority: $level\r\nX-MSMail-Priority: Normal");
  472. }
  473. }
  474. /**
  475. * Set the encoding to use in headers
  476. * @param string mode
  477. */
  478. public function setHeaderEncoding($mode='B')
  479. {
  480. switch (strtoupper($mode))
  481. {
  482. case 'Q':
  483. case 'QP':
  484. case 'QUOTED-PRINTABLE':
  485. $this->headerEncoding = 'Q';
  486. break;
  487. default:
  488. $this->headerEncoding = 'B';
  489. }
  490. }
  491. /**
  492. * Set the return path address (Bounce detection)
  493. * @param string address
  494. */
  495. public function setReturnPath($address)
  496. {
  497. $this->returnPath = $this->getAddress($address);
  498. }
  499. /**
  500. * Request a read receipt from all recipients
  501. * @param bool request receipt
  502. */
  503. public function requestReadReceipt($request=true)
  504. {
  505. $this->readReceipt = (bool) $request;
  506. }
  507. /**
  508. * Set the character encoding were using
  509. * @param string charset
  510. */
  511. public function setCharset($string)
  512. {
  513. $this->charset = $string;
  514. $this->userCharset = $string;
  515. }
  516. /**
  517. * Whether or not Swift should send unique emails to all To recipients
  518. * @param bool unique
  519. */
  520. public function useExactCopy($use=true)
  521. {
  522. $this->useExactCopy = (bool) $use;
  523. }
  524. /**
  525. * Get the return path recipient
  526. */
  527. public function getReturnPath()
  528. {
  529. return $this->returnPath;
  530. }
  531. /**
  532. * Get the sender
  533. */
  534. public function getFromAddress()
  535. {
  536. return $this->from;
  537. }
  538. /**
  539. * Get Cc recipients
  540. */
  541. public function getCcAddresses()
  542. {
  543. return $this->Cc;
  544. }
  545. /**
  546. * Get Bcc addresses
  547. */
  548. public function getBccAddresses()
  549. {
  550. return $this->Bcc;
  551. }
  552. /**
  553. * Get To addresses
  554. */
  555. public function getToAddresses()
  556. {
  557. return $this->to;
  558. }
  559. /**
  560. * Get the list of failed recipients
  561. * @return array recipients
  562. */
  563. public function getFailedRecipients()
  564. {
  565. return $this->failedAddresses;
  566. }
  567. /**
  568. * Return the array of errors (if any)
  569. * @return array errors
  570. */
  571. public function getErrors()
  572. {
  573. return $this->errors;
  574. }
  575. /**
  576. * Return the conversation up to maxLogSize between the SMTP server and swift
  577. * @return array transactions
  578. */
  579. public function getTransactions()
  580. {
  581. return $this->transactions;
  582. }
  583. /**
  584. * Sets the Reply-To address used for sending mail
  585. * @param string address
  586. */
  587. public function setReplyTo($string)
  588. {
  589. $this->replyTo = $this->getAddress($string);
  590. }
  591. /**
  592. * Add one or more Blind-carbon-copy recipients to the mail
  593. * @param mixed addresses
  594. */
  595. public function addBcc($addresses)
  596. {
  597. $this->Bcc = array_merge($this->Bcc, $this->parseAddressList((array) $addresses));
  598. }
  599. /**
  600. * Add one or more Carbon-copy recipients to the mail
  601. * @param mixed addresses
  602. */
  603. public function addCc($addresses)
  604. {
  605. $this->Cc = array_merge($this->Cc, $this->parseAddressList((array) $addresses));
  606. }
  607. /**
  608. * Force swift to break lines longer than 76 characters long
  609. * @param bool resize
  610. */
  611. public function useAutoLineResizing($use=true)
  612. {
  613. $this->autoCompliance = (bool) $use;
  614. }
  615. /**
  616. * Associate a code with a command. Swift will fail quietly if the code
  617. * returned does not match.
  618. * @param string command
  619. * @param int code
  620. */
  621. public function addExpectedCode($command, $code)
  622. {
  623. $this->expectedCodes[$command] = (int) $code;
  624. }
  625. /**
  626. * Reads the EHLO return string to see what AUTH methods are supported
  627. * @param string EHLO response
  628. * @return void
  629. * @private
  630. */
  631. private function getAuthenticationMethods($list)
  632. {
  633. preg_match("/^250[\-\ ]AUTH\ (.*)\r\n/m", $list, $matches);
  634. if (!empty($matches[1]))
  635. {
  636. $types = explode(' ', $matches[1]);
  637. $this->authTypes = $types;
  638. }
  639. }
  640. /**
  641. * Load a plugin object into Swift
  642. * @param object Swift_IPlugin
  643. * @param string plugin name
  644. * @return void
  645. */
  646. public function loadPlugin(Swift_IPlugin &$object, $id=false)
  647. {
  648. if ($id) $object->pluginName = $id;
  649. $this->plugins[$object->pluginName] =& $object;
  650. $this->plugins[$object->pluginName]->loadBaseObject($this);
  651. if (method_exists($this->plugins[$object->pluginName], 'onLoad'))
  652. {
  653. $this->plugins[$object->pluginName]->onLoad();
  654. }
  655. }
  656. /**
  657. * Fetch a reference to a plugin in Swift
  658. * @param string plugin name
  659. * @return object Swift_IPlugin
  660. */
  661. public function &getPlugin($name)
  662. {
  663. if (isset($this->plugins[$name]))
  664. {
  665. return $this->plugins[$name];
  666. }
  667. }
  668. /**
  669. * Un-plug a loaded plugin. Returns false on failure.
  670. * @param string plugin_name
  671. * @return bool success
  672. */
  673. public function removePlugin($name)
  674. {
  675. if (!isset($this->plugins[$name])) return false;
  676. if (method_exists($this->plugins[$name], 'onUnload'))
  677. {
  678. $this->plugins[$name]->onUnload();
  679. }
  680. unset($this->plugins[$name]);
  681. return true;
  682. }
  683. /**
  684. * Return the number of plugins loaded
  685. * @return int plugins
  686. */
  687. public function numPlugins()
  688. {
  689. return count($this->plugins);
  690. }
  691. /**
  692. * Trigger event handlers
  693. * @param string event handler
  694. * @return void
  695. * @private
  696. */
  697. private function triggerEventHandler($func)
  698. {
  699. foreach ($this->plugins as $name => $object)
  700. {
  701. if (method_exists($this->plugins[$name], $func))
  702. {
  703. $this->plugins[$name]->$func();
  704. }
  705. }
  706. }
  707. /**
  708. * Attempt to load any authenticators from the Swift/ directory
  709. * @see RFC 2554
  710. * @return void
  711. * @private
  712. */
  713. private function loadDefaultAuthenticators()
  714. {
  715. $dir = dirname(__FILE__).'/Swift/Authenticator';
  716. if (file_exists($dir) && is_dir($dir))
  717. {
  718. $handle = opendir($dir);
  719. while ($file = readdir($handle))
  720. {
  721. if (preg_match('@^([a-zA-Z\d]*)\.php$@', $file, $matches))
  722. {
  723. require_once($dir.'/'.$file);
  724. $class = 'Swift_Authenticator_'.$matches[1];
  725. $this->loadAuthenticator(new $class);
  726. }
  727. }
  728. closedir($handle);
  729. }
  730. }
  731. /**
  732. * Use SMTP authentication
  733. * @param string username
  734. * @param string password
  735. * @return bool successful
  736. */
  737. public function authenticate($username, $password)
  738. {
  739. $this->username = $username;
  740. $this->password = $password;
  741. if (empty($this->authenticators)) $this->loadDefaultAuthenticators();
  742. if (!$this->esmtp || empty($this->authTypes))
  743. {
  744. $this->logError('The MTA doesn\'t support any of Swift\'s loaded authentication mechanisms', 0);
  745. return false;
  746. }
  747. foreach ($this->authenticators as $name => $object)
  748. {
  749. //An asterisk means that the auth type is not advertised by ESMTP
  750. if (in_array($name, $this->authTypes) || substr($name, 0, 1) == '*')
  751. {
  752. if ($this->authenticators[$name]->run($username, $password))
  753. {
  754. $this->triggerEventHandler('onAuthenticate');
  755. return true;
  756. }
  757. else return false;
  758. }
  759. }
  760. //If we get this far, no authenticators were used
  761. $this->logError('The MTA doesn\'t support any of Swift\'s loaded authentication mechanisms', 0);
  762. $this->fail();
  763. return false;
  764. }
  765. /**
  766. * Load an authentication mechanism object into Swift
  767. * @param object Swift_IAuthenticator
  768. * @return void
  769. */
  770. public function loadAuthenticator(Swift_IAuthenticator &$object)
  771. {
  772. $this->authenticators[$object->serverString] =& $object;
  773. $this->authenticators[$object->serverString]->loadBaseObject($this);
  774. }
  775. /**
  776. * Get a unique multipart MIME boundary
  777. * @param string mail data, optional
  778. * @return string boundary
  779. * @private
  780. */
  781. private function getMimeBoundary($string=false)
  782. {
  783. $force = true;
  784. if (!$string)
  785. {
  786. $force = false;
  787. $string = implode('', $this->parts);
  788. $string .= implode('', $this->attachments);
  789. }
  790. if ($this->mimeBoundary && !$force) return $this->mimeBoundary;
  791. else
  792. { //Make sure we don't (as if it would ever happen!) -
  793. // produce a boundary that's actually in the email already
  794. do
  795. {
  796. $this->mimeBoundary = '_=_swift-'.uniqid(rand(), true);
  797. } while(strpos($string, $this->mimeBoundary));
  798. }
  799. return $this->mimeBoundary;
  800. }
  801. /**
  802. * Append a string to the message header
  803. * @param string headers
  804. * @return void
  805. */
  806. public function addHeaders($string)
  807. {
  808. $this->headers .= preg_replace("/(?:\r|\n|^)[^:]*?:\ *(.*?)(?:\r|\n|$)/me", 'str_replace("$1", $this->safeEncodeHeader("$1"), "$0")', $string);
  809. if (substr($this->headers, -2) != "\r\n")
  810. $this->headers .= "\r\n";
  811. }
  812. /**
  813. * Set the multipart MIME boundary (only works for first part)
  814. * @param string boundary
  815. * @return void
  816. */
  817. public function setMimeBoundary($string)
  818. {
  819. $this->mimeBoundary = $string;
  820. }
  821. /**
  822. * Set the text that displays in non-MIME clients
  823. * @param string warning
  824. * @return void
  825. */
  826. public function setMimeWarning($warning)
  827. {
  828. $this->mimeWarning = $warning;
  829. }
  830. /**
  831. * Tells Swift to clear out attachment, parts, headers etc
  832. * automatically upon sending - this is the default.
  833. * @param bool flush
  834. */
  835. public function autoFlush($flush=true)
  836. {
  837. $this->autoFlush = (bool) $flush;
  838. }
  839. /**
  840. * Empty out the MIME parts and attachments
  841. * @param bool reset headers
  842. * @return void
  843. */
  844. public function flush($clear_headers=false)
  845. {
  846. $this->parts = array();
  847. $this->attachments = array();
  848. $this->images = array();
  849. $this->mimeBoundary = null;
  850. $this->Bcc = array();
  851. $this->to = array();
  852. $this->Cc = array();
  853. $this->replyTo = null;
  854. //See comment above the headers property above the constructor before editing this line! *
  855. if ($clear_headers) $this->headers = "X-Mailer: Swift ".SWIFT_VERSION." by Chris Corbyn\r\n";
  856. $this->triggerEventHandler('onFlush');
  857. }
  858. /**
  859. * Reset to
  860. */
  861. public function flushTo()
  862. {
  863. $this->to = array();
  864. }
  865. /**
  866. * Reset Cc
  867. */
  868. public function flushCc()
  869. {
  870. $this->Cc = array();
  871. }
  872. /**
  873. * Reset Bcc
  874. */
  875. public function flushBcc()
  876. {
  877. $this->Bcc = array();
  878. }
  879. /**
  880. * Reset parts
  881. */
  882. public function flushParts()
  883. {
  884. $this->parts = array();
  885. $this->images = array();
  886. }
  887. /**
  888. * Reset attachments
  889. */
  890. public function flushAttachments()
  891. {
  892. $this->attachments = array();
  893. }
  894. /**
  895. * Reset headers
  896. */
  897. public function flushHeaders()
  898. {
  899. $this->headers = "X-Mailer: Swift ".SWIFT_VERSION." by Chris Corbyn\r\n";
  900. }
  901. /**
  902. * Log an error in Swift::errors
  903. * @param string error string
  904. * @param int error number
  905. * @return void
  906. */
  907. public function logError($errstr, $errno=0)
  908. {
  909. $this->errors[] = array(
  910. 'num' => $errno,
  911. 'time' => microtime(),
  912. 'message' => $errstr
  913. );
  914. $this->lastError = $errstr;
  915. $this->triggerEventHandler('onError');
  916. }
  917. /**
  918. * Log a transaction in Swift::transactions
  919. * @param string command
  920. * @return void
  921. */
  922. public function logTransaction($command='')
  923. {
  924. $this->lastTransaction = array(
  925. 'command' => $command,
  926. 'time' => microtime(),
  927. 'response' => $this->getResponse()
  928. );
  929. $this->triggerEventHandler('onLog');
  930. if ($this->maxLogSize)
  931. {
  932. $this->transactions = array_slice(array_merge($this->transactions, array($this->lastTransaction)), -$this->maxLogSize);
  933. }
  934. else $this->transactions[] = $this->lastTransaction;
  935. }
  936. /**
  937. * Read the data from the socket
  938. * @return string response
  939. * @private
  940. */
  941. private function getResponse()
  942. {
  943. if (!$this->connection->readHook || !$this->isConnected()) return false;
  944. $ret = "";
  945. while (true)
  946. {
  947. $tmp = @fgets($this->connection->readHook);
  948. $ret .= $tmp;
  949. //The last line of SMTP replies have a space after the status number
  950. // They do NOT have an EOF so while(!feof($socket)) will hang!
  951. if (substr($tmp, 3, 1) == ' ' || $tmp == false) break;
  952. }
  953. $this->responseCode = $this->getResponseCode($ret);
  954. $this->lastResponse = $ret;
  955. $this->triggerEventHandler('onResponse');
  956. return $this->lastResponse;
  957. }
  958. /**
  959. * Get the number of the last server response
  960. * @param string response string
  961. * @return int response code
  962. * @private
  963. */
  964. private function getResponseCode($string)
  965. {
  966. return (int) sprintf("%d", $string);
  967. }
  968. /**
  969. * Get the first word of the command
  970. * @param string command
  971. * @return string keyword
  972. * @private
  973. */
  974. private function getCommandKeyword($comm)
  975. {
  976. if (false !== $pos = strpos($comm, ' '))
  977. {
  978. return $this->commandKeyword = strtolower(substr($comm, 0, $pos));
  979. }
  980. else return $this->commandKeyword = strtolower(trim($comm));
  981. }
  982. /**
  983. * Send a reset command in the event of a problem
  984. */
  985. public function reset()
  986. {
  987. $this->command("RSET\r\n");
  988. }
  989. /**
  990. * Issue a command to the socket
  991. * @param string command
  992. * @return string response
  993. */
  994. public function command($comm)
  995. {
  996. //We'll usually ignore a certain sequence of commands if something screwed up
  997. if ($this->ignoreCommands)
  998. {
  999. $this->skippedCommands++;
  1000. if ($this->skippedCommands >= $this->ignoreCommands)
  1001. {
  1002. $this->responseCode = -2; //Done (internal to swift)
  1003. $this->ignoreCommands = 0;
  1004. $this->skippedCommands = 0;
  1005. }
  1006. return true;
  1007. }
  1008. $this->currentCommand = ltrim($comm);
  1009. $this->triggerEventHandler('onBeforeCommand');
  1010. if (!$this->connection->writeHook || !$this->isConnected() || $this->failed)
  1011. {
  1012. $this->logError('Error running command: '.trim($comm).'. No connection available', 0);
  1013. return false;
  1014. }
  1015. $command_keyword = $this->getCommandKeyword($this->currentCommand);
  1016. //We successfully got as far as asking to send the email so we can forget any failed addresses for now
  1017. if ($command_keyword != 'rcpt' && $command_keyword != 'rset') $this->subFailCount = 0;
  1018. //SMTP commands must end with CRLF
  1019. if (substr($this->currentCommand, -2) != "\r\n") $this->currentCommand .= "\r\n";
  1020. if (@fwrite($this->connection->writeHook, $this->currentCommand))
  1021. {
  1022. $this->logTransaction($this->currentCommand);
  1023. if (array_key_exists($command_keyword, $this->expectedCodes))
  1024. {
  1025. if ($this->expectedCodes[$command_keyword] != $this->responseCode)
  1026. {
  1027. //If a recipient was rejected
  1028. if ($command_keyword == 'rcpt')
  1029. {
  1030. $this->failCount++;
  1031. $this->failedAddresses[] = $this->getAddress($comm);
  1032. //Some addresses may still work...
  1033. if (++$this->subFailCount >= $this->numAddresses)
  1034. {
  1035. //Sending failed, just RSET and don't send data to this recipient
  1036. $this->reset();
  1037. //So we can still cache the mail body in send()
  1038. $this->responseCode = -1; //Pending (internal to swift)
  1039. //Skip the next two commands (DATA and <mail>)
  1040. $this->ignoreCommands = 2;
  1041. $this->logError('Send Error: Sending to '.$this->subFailCount.' recipients rejected (bad response code).', $this->responseCode);
  1042. //But don't fail here.... these are usually not fatal
  1043. }
  1044. }
  1045. else
  1046. {
  1047. $this->fail();
  1048. $this->logError('MTA Error (Swift was expecting response code '.$this->expectedCodes[$command_keyword].' but got '.$this->responseCode.'): '.$this->lastResponse, $this->responseCode);
  1049. return $this->hasFailed();
  1050. }
  1051. }
  1052. }
  1053. $this->triggerEventHandler('onCommand');
  1054. return $this->lastResponse;
  1055. }
  1056. else return false;
  1057. }
  1058. /**
  1059. * Splits lines longer than 76 characters to multiple lines
  1060. * @param string text
  1061. * @return string chunked output
  1062. */
  1063. public function chunkSplitLines($string)
  1064. {
  1065. return wordwrap($string, 74, "\r\n");
  1066. }
  1067. /**
  1068. * Add a part to a multipart message
  1069. * @param string body
  1070. * @param string content-type, optional
  1071. * @param string content-transfer-encoding, optional
  1072. * @return void
  1073. */
  1074. public function addPart($string, $type='text/plain', $encoding=false)
  1075. {
  1076. if (!$this->userCharset && (strtoupper($this->charset) != 'UTF-8') && $this->detectUTF8($string)) $this->charset = 'UTF-8';
  1077. if (!$encoding && $this->_8bitmime) $encoding = '8bit';
  1078. elseif (!$encoding) $encoding = 'quoted-printable';
  1079. $body_string = $this->encode($string, $encoding);
  1080. if ($this->autoCompliance && $encoding != 'binary') $body_string = $this->chunkSplitLines($body_string);
  1081. $ret = "Content-Type: $type; charset=\"{$this->charset}\"; format=flowed\r\n".
  1082. "Content-Transfer-Encoding: $encoding\r\n\r\n".
  1083. $body_string;
  1084. if (strtolower($type) == 'text/html') $this->parts[] = $this->makeSafe($ret);
  1085. else $this->parts = array_merge((array) $this->makeSafe($ret), $this->parts);
  1086. }
  1087. /**
  1088. * Get the current number of parts in the email
  1089. * @return int num parts
  1090. */
  1091. public function numParts()
  1092. {
  1093. return count($this->parts);
  1094. }
  1095. /**
  1096. * Add an attachment to a multipart message.
  1097. * Attachments are added as base64 encoded data.
  1098. * @param string data
  1099. * @param string filename
  1100. * @param string content-type
  1101. * @return void
  1102. */
  1103. public function addAttachment($data, $filename, $type='application/octet-stream')
  1104. {
  1105. $this->attachments[] = "Content-Type: $type; ".
  1106. "name=\"$filename\";\r\n".
  1107. "Content-Transfer-Encoding: base64\r\n".
  1108. "Content-Description: $filename\r\n".
  1109. "Content-Disposition: attachment; ".
  1110. "filename=\"$filename\"\r\n\r\n".
  1111. chunk_split($this->encode($data, 'base64'));
  1112. }
  1113. /**
  1114. * Get the current number of attachments in the mail
  1115. * @return int num attachments
  1116. */
  1117. public function numAttachments()
  1118. {
  1119. return count($this->attachments);
  1120. }
  1121. /**
  1122. * Insert an inline image and return it's name
  1123. * These work like attachments but have a content-id
  1124. * and are inline/related.
  1125. * @param string path
  1126. * @return string name
  1127. */
  1128. public function addImage($path)
  1129. {
  1130. if (!file_exists($path)) return false;
  1131. $gpc = ini_get('magic_quotes_gpc');
  1132. ini_set('magic_quotes_gpc', 0);
  1133. $gpc_run = ini_get('magic_quotes_runtime');
  1134. ini_set('magic_quotes_runtime', 0);
  1135. $img_data = @getimagesize($path);
  1136. if (!$img_data) return false;
  1137. $type = image_type_to_mime_type($img_data[2]);
  1138. $filename = basename($path);
  1139. $data = file_get_contents($path);
  1140. $cid = 'SWM'.md5(uniqid(rand(), true));
  1141. $this->images[] = "Content-Type: $type\r\n".
  1142. "Content-Transfer-Encoding: base64\r\n".
  1143. "Content-Disposition: inline; ".
  1144. "filename=\"$filename\"\r\n".
  1145. "Content-ID: <$cid>\r\n\r\n".
  1146. chunk_split($this->encode($data, 'base64'));
  1147. ini_set('magic_quotes_gpc', $gpc);
  1148. ini_set('magic_quotes_runtime', $gpc_run);
  1149. return 'cid:'.$cid;
  1150. }
  1151. /**
  1152. * Insert an inline file and return it's name
  1153. * These work like attachments but have a content-id
  1154. * and are inline/related.
  1155. * The data is the file contents itself (binary safe)
  1156. * @param string file contents
  1157. * @param string content-type
  1158. * @param string filename
  1159. * @param string content-id
  1160. * @return string name
  1161. */
  1162. public function embedFile($data, $type='application/octet-stream', $filename=false, $cid=false)
  1163. {
  1164. if (!$cid) $cid = 'SWM'.md5(uniqid(rand(), true));
  1165. if (!$filename) $filename = $cid;
  1166. $this->images[] = "Content-Type: $type\r\n".
  1167. "Content-Transfer-Encoding: base64\r\n".
  1168. "Content-Disposition: inline; ".
  1169. "filename=\"$filename\"\r\n".
  1170. "Content-ID: <$cid>\r\n\r\n".
  1171. chunk_split($this->encode($data, 'base64'));
  1172. return 'cid:'.$cid;
  1173. }
  1174. /**
  1175. * Close the connection in the connecion object
  1176. * @return void
  1177. */
  1178. public function close()
  1179. {
  1180. if ($this->connection->writeHook && $this->isConnected())
  1181. {
  1182. $this->command("QUIT\r\n");
  1183. $this->connection->stop();
  1184. }
  1185. $this->triggerEventHandler('onClose');
  1186. }
  1187. /**
  1188. * Check if Swift has failed and stopped processing
  1189. * @return bool failed
  1190. */
  1191. public function hasFailed()
  1192. {
  1193. return $this->failed;
  1194. }
  1195. /**
  1196. * Force Swift to fail and stop processing
  1197. * @return void
  1198. */
  1199. public function fail()
  1200. {
  1201. $this->failed = true;
  1202. $this->triggerEventHandler('onFail');
  1203. }
  1204. /**
  1205. * Detect if a string contains multi-byte non-ascii chars that fall in the UTF-8 tanges
  1206. * @param mixed input
  1207. * @return bool
  1208. */
  1209. public function detectUTF8($string_in)
  1210. {
  1211. foreach ((array)$string_in as $string)
  1212. {
  1213. if (is_array($string))
  1214. {
  1215. if ($this->detectUTF8($string)) return true;
  1216. }
  1217. elseif (preg_match('%(?:
  1218. [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
  1219. |\xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
  1220. |[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
  1221. |\xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
  1222. |\xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
  1223. |[\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
  1224. |\xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
  1225. )+%xs', $string)) return true;
  1226. }
  1227. return false;
  1228. }
  1229. /**
  1230. * This function checks for 7bit *printable* characters
  1231. * which excludes \r \n \t etc and so, is safe for use in mail headers
  1232. * Actual permitted chars [\ !"#\$%&'\(\)\*\+,-\.\/0123456789:;<=>\?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]\^_`abcdefghijklmnopqrstuvwxyz{\|}~]
  1233. * Ranges \x00-\x1F are printer control sequences
  1234. * \x7F is the ascii delete character
  1235. * @param string input
  1236. * @return bool
  1237. */
  1238. public function is7bitPrintable($string)
  1239. {
  1240. if (preg_match('/^[\x20-\x7E]*$/D', $string)) return true;
  1241. else return false;
  1242. }
  1243. /**
  1244. * This is harsh! It makes things safe for sending as command sequences in SMTP
  1245. * Specifically the enveloping. We could be extremely strict and implement what I planned on doing
  1246. * here: http://www.swiftmailer.org/contrib/proposed-address-test.txt
  1247. * @param string input
  1248. * @return safe output
  1249. */
  1250. public function make7bitPrintable($string)
  1251. {
  1252. return preg_replace('/[^\x20-\x7E]/', '', $string);
  1253. }
  1254. /**
  1255. * Encode a string (mail) in a given format
  1256. * Currently supports:
  1257. * - BASE64
  1258. * - Quoted-Printable
  1259. * - Ascii 7-bit
  1260. * - Ascii 8bit
  1261. * - Binary (not encoded)
  1262. *
  1263. * @param string input
  1264. * @param string encoding
  1265. * @return string encoded output
  1266. */
  1267. public function encode($string, $type, $maxlen=false)
  1268. {
  1269. $type = strtolower($type);
  1270. switch ($type)
  1271. {
  1272. case 'base64':
  1273. return base64_encode($string);
  1274. break;
  1275. //
  1276. case 'quoted-printable':
  1277. return $this->quotedPrintableEncode($string, $maxlen);
  1278. //
  1279. case '7bit':
  1280. case '8bit':
  1281. break;
  1282. case 'binary':
  1283. default:
  1284. break;
  1285. }
  1286. return $string;
  1287. }
  1288. /**
  1289. * Headers cannot have non-ascii or high ascii chars in them
  1290. * @param mixed input
  1291. * @return string encoded output (with encoding type)
  1292. */
  1293. public function safeEncodeHeader($string)
  1294. {
  1295. if (!is_array($string))
  1296. {
  1297. if ($this->is7bitPrintable($string)) return $string;
  1298. else
  1299. {
  1300. //Check if the string contains address notation
  1301. $address_start = strrpos($string, '<');
  1302. $address_end = strrpos($string, '>');
  1303. $address = '';
  1304. //If the < and > are in the correct places
  1305. if (($address_start !== false) && $address_start < $address_end)
  1306. {
  1307. //Then store the email address
  1308. $address = substr($string, $address_start, ($address_end-$address_start+1));
  1309. if (!$this->is7bitPrintable($address)) $address = $this->make7bitPrintable($address);
  1310. //... and remove it from the string
  1311. $string = substr($string, 0, $address_start);
  1312. }
  1313. if ($this->headerEncoding == 'B') $encoded = trim(chunk_split($this->encode($string, 'base64')));
  1314. else
  1315. {
  1316. $this->headerEncoding = 'Q';
  1317. $encoded = trim($this->encode($string, 'quoted-printable'));
  1318. }
  1319. $lines = explode("\r\n", $encoded);
  1320. return '=?'.$this->charset.'?'.$this->headerEncoding.'?'.implode("?=\r\n =?{$this->charset}?{$this->headerEncoding}?", $lines).'?= '.$address;
  1321. }
  1322. }
  1323. else
  1324. {
  1325. $ret = array();
  1326. foreach ($string as $line)
  1327. {
  1328. $ret[] = $this->safeEncodeHeader($line); //Recurse
  1329. }
  1330. return $ret;
  1331. }
  1332. }
  1333. /**
  1334. * Handles quoted-printable encoding
  1335. * From php.net by user bendi at interia dot pl
  1336. * @param string input
  1337. * @param int maxlength
  1338. * @return string encoded output
  1339. * @private
  1340. */
  1341. private function quotedPrintableEncode($string, $maxlen=false)
  1342. {
  1343. if (!$maxlen) $maxlen = 73;
  1344. $string = preg_replace('/[^\x21-\x3C\x3E-\x7E\x09\x20]/e', 'sprintf( "=%02x", ord ( "$0" ) ) ;', $string);
  1345. preg_match_all('/.{1,'.$maxlen.'}([^=]{0,3})?/', $string, $matches);
  1346. $sep = "=\r\n";
  1347. return implode($sep, $matches[0]);
  1348. }
  1349. /**
  1350. * Converts lone LF characters to CRLF
  1351. * @param string input
  1352. * @return string converted output
  1353. */
  1354. public function LFtoCRLF($string)
  1355. {
  1356. return preg_replace("@(?:(?<!\r)\n)|(?:\r(?!\n))@", "\r\n", $string);
  1357. }
  1358. /**
  1359. * Prevents premature <CRLF>.<CRLF> strings
  1360. * Converts any lone LF characters to CRLF
  1361. * @param string input
  1362. * @return string escaped output
  1363. */
  1364. public function makeSafe($string)
  1365. {
  1366. return str_replace("\r\n.", "\r\n..", $this->LFtoCRLF($string));
  1367. }
  1368. /**
  1369. * Pulls an email address from a "Name" <add@ress> string
  1370. * @param string input
  1371. * @return string address
  1372. */
  1373. public function getAddress($string)
  1374. {
  1375. if (!$string) return null;
  1376. if (preg_match('/^.*?<([^>]+)>\s*$/s', $string, $matches))
  1377. {
  1378. return '<'.$this->make7bitPrintable($matches[1]).'>';
  1379. }
  1380. elseif (!preg_match('/<|>/', $string)) return '<'.$this->make7bitPrintable($string).'>';
  1381. else return $this->make7bitPrintable($string);
  1382. }
  1383. /**
  1384. * Builds the headers needed to reflect who the mail is sent to
  1385. * Presently this is just the "To: " header
  1386. * @param string address
  1387. * @return string headers
  1388. * @private
  1389. */
  1390. private function makeRecipientHeaders($address=false)
  1391. {
  1392. if ($address) return "To: ".$this->safeEncodeHeader($address)."\r\n";
  1393. else
  1394. {
  1395. $ret = "To: ".implode(",\r\n\t", $this->safeEncodeHeader($this->to))."\r\n";
  1396. if (!empty($this->Cc)) $ret .= "Cc: ".implode(",\r\n\t", $this->safeEncodeHeader($this->Cc))."\r\n";
  1397. return $ret;
  1398. }
  1399. }
  1400. /**
  1401. * Structure a given array of addresses into the 1-dim we want
  1402. * @param array unstructured
  1403. * @return array structured
  1404. * @private
  1405. */
  1406. private function parseAddressList($u_array)
  1407. {
  1408. $ret = array();
  1409. foreach ($u_array as $val)
  1410. {
  1411. if (is_array($val)) $ret[] = '"'.$val[0].'" <'.$val[1].'>';
  1412. else $ret[] = $val;
  1413. }
  1414. return $ret;
  1415. }
  1416. /**
  1417. * Send an email using Swift (send commands)
  1418. * @param string to_address
  1419. * @param string from_address
  1420. * @param string subject
  1421. * @param string body, optional
  1422. * @param string content-type,optional
  1423. * @param string content-transfer-encoding,optional
  1424. * @return bool successful
  1425. */
  1426. public function send($to, $from, $subject, $body=false, $type='text/plain', $encoding=false)
  1427. {
  1428. if ((strtoupper($this->charset) != 'UTF-8') && $body && $this->detectUTF8($body) && !$this->userCharset) $this->charset = 'UTF-8';
  1429. if ((strtoupper($this->charset) != 'UTF-8') && $this->detectUTF8($subject) && !$this->userCharset) $this->charset = 'UTF-8';
  1430. if ((strtoupper($this->charset) != 'UTF-8') && $this->detectUTF8($to) && !$this->userCharset) $this->charset = 'UTF-8';
  1431. if ((strtoupper($this->charset) != 'UTF-8') && $this->detectUTF8($from) && !$this->userCharset) $this->charset = 'UTF-8';
  1432. if (!$encoding && $this->_8bitmime) $encoding = '8bit';
  1433. elseif (!$encoding) $encoding = 'quoted-printable';
  1434. $to = (array) $to;
  1435. $this->to = $this->parseAddressList($to);
  1436. //In these cases we just send the one email
  1437. if ($this->useExactCopy || !empty($this->Cc) || !empty($this->Bcc))
  1438. {
  1439. $this->currentMail = $this->buildMail(false, $from, $subject, $body, $type, $encoding, 1);
  1440. $this->triggerEventHandler('onBeforeSend');
  1441. foreach ($this->currentMail as $command)
  1442. {
  1443. //Number of successful addresses expected
  1444. $this->numAddresses = 1;
  1445. if (is_array($command))
  1446. { //Commands can be returned as 1-dimensional arrays
  1447. $this->numAddresses = count($command);
  1448. foreach ($command as $c)
  1449. {
  1450. if (!$this->command($c))
  1451. {
  1452. $this->logError('Sending failed on command: '.$c, 0);
  1453. return false;
  1454. }
  1455. }
  1456. }
  1457. else if (!$this->command($command))
  1458. {
  1459. $this->logError('Sending failed on command: '.$command, 0);
  1460. return false;
  1461. }
  1462. }
  1463. $this->triggerEventHandler('onSend');
  1464. }
  1465. else
  1466. {
  1467. $get_body = true;
  1468. $cached_body = '';
  1469. foreach ($this->to as $address)
  1470. {
  1471. $this->currentMail = $this->buildMail($address, $from, $subject, $body, $type, $encoding, $get_body);
  1472. //If we have a cached version
  1473. if (!$get_body) $this->currentMail[] = $this->makeRecipientHeaders($address).$cached_body;
  1474. $this->triggerEventHandler('onBeforeSend');
  1475. foreach ($this->currentMail as $command)
  1476. {
  1477. //This means we're about to send the DATA part
  1478. if ($get_body && ($this->responseCode == 354 || $this->responseCode == -1))
  1479. {
  1480. $cached_body = $command;
  1481. $command = $this->makeRecipientHeaders($address).$command;
  1482. }
  1483. if (is_array($command))
  1484. {
  1485. foreach ($command as $c)
  1486. {
  1487. if (!$this->command($c))
  1488. {
  1489. $this->logError('Sending failed on command: '.$c, 0);
  1490. return false;
  1491. }
  1492. }
  1493. }
  1494. else if (!$this->command($command))
  1495. {
  1496. $this->logError('Sending failed on command: '.$command, 0);
  1497. return false;
  1498. }
  1499. }
  1500. $this->triggerEventHandler('onSend');
  1501. $get_body = false;
  1502. }
  1503. }
  1504. if ($this->autoFlush) $this->flush(true); //Tidy up a bit
  1505. return true;
  1506. }
  1507. /**
  1508. * Builds the list of commands to send the email
  1509. * The last command in the output is the email itself (DATA)
  1510. * The commands are as follows:
  1511. * - MAIL FROM: <address> (0)
  1512. * - RCPT TO: <address> (1)
  1513. * - DATA (2)
  1514. * - <email> (3)
  1515. *
  1516. * @param string to_address
  1517. * @param string from_address
  1518. * @param string subject
  1519. * @param string body, optional
  1520. * @param string content-type, optional
  1521. * @param string encoding, optional
  1522. * @return array commands
  1523. * @private
  1524. */
  1525. private function buildMail($to, $from, $subject, $body, $type='text/plain', $encoding='8bit', $return_data_part=true)
  1526. {
  1527. $date = date('r'); //RFC 2822 date
  1528. $return_path = $this->returnPath ? $this->returnPath : $this->getAddress($from);
  1529. $ret = array("MAIL FROM: ".$return_path."\r\n"); //Always
  1530. //If the user specifies a different reply-to
  1531. $reply_to = !empty($this->replyTo) ? $this->getAddress($this->replyTo) : $this->getAddress($from);
  1532. //Standard headers
  1533. $this->from = $from;
  1534. $data = "From: ".$this->safeEncodeHeader($from)."\r\n".
  1535. "Reply-To: ".$this->safeEncodeHeader($reply_to)."\r\n".
  1536. "Subject: ".$this->safeEncodeHeader($subject)."\r\n".
  1537. "Date: $date\r\n";
  1538. if ($this->readReceipt) $data .= "Disposition-Notification-To: ".$this->safeEncodeHeader($from)."\r\n";
  1539. if (!$to) //Only need one mail if no address was given
  1540. { //We'll collate the addresses from the class properties
  1541. $data .= $this->getMimeBody($body, $type, $encoding)."\r\n.\r\n";
  1542. $headers = $this->makeRecipientHeaders();
  1543. //Rcpt can be run several times
  1544. $rcpt = array();
  1545. foreach ($this->to as $address) $rcpt[] = "RCPT TO: ".$this->getAddress($address)."\r\n";
  1546. foreach ($this->Cc as $address) $rcpt[] = "RCPT TO: ".$this->getAddress($address)."\r\n";
  1547. $ret[] = $rcpt;
  1548. $ret[] = "DATA\r\n";
  1549. $ret[] = $headers.$this->headers.$data;
  1550. //Bcc recipients get to see their own Bcc header but nobody else's
  1551. foreach ($this->Bcc as $address)
  1552. {
  1553. $ret[] = "MAIL FROM: ".$this->getAddress($from)."\r\n";
  1554. $ret[] = "RCPT TO: ".$this->getAddress($address)."\r\n";
  1555. $ret[] = "DATA\r\n";
  1556. $ret[] = $headers."Bcc: ".$this->safeEncodeHeader($address)."\r\n".$this->headers.$data;
  1557. }
  1558. }
  1559. else //Just make this individual email
  1560. {
  1561. if ($return_data_part) $mail_body = $this->getMimeBody($body, $type, $encoding);
  1562. $ret[] = "RCPT TO: ".$this->getAddress($to)."\r\n";
  1563. $ret[] = "DATA\r\n";
  1564. if ($return_data_part) $ret[] = $data.$this->headers.$mail_body."\r\n.\r\n";
  1565. }
  1566. return $ret;
  1567. }
  1568. /**
  1569. * Returns the MIME-specific headers followed by the email
  1570. * content as a string.
  1571. * @param string body
  1572. * @param string content-type
  1573. * @param string encoding
  1574. * @return string mime data
  1575. * @private
  1576. */
  1577. private function getMimeBody($string, $type, $encoding)
  1578. {
  1579. if ($string) //Not using MIME parts
  1580. {
  1581. $body = $this->encode($string, $encoding);
  1582. if ($this->autoCompliance) $body = $this->chunkSplitLines($body);
  1583. $data = "Content-Type: $type; charset=\"{$this->charset}\"; format=flowed\r\n".
  1584. "Content-Transfer-Encoding: $encoding\r\n\r\n".
  1585. $this->makeSafe($body);
  1586. }
  1587. else
  1588. { //Build a full email from the parts we have
  1589. $boundary = $this->getMimeBoundary();
  1590. $encoding = '8bit';
  1591. $mixalt = 'alternative';
  1592. $alternative_boundary = $this->getMimeBoundary(implode($this->parts));
  1593. if (!empty($this->images))
  1594. {
  1595. $mixalt = 'mixed';
  1596. $related_boundary = $this->getMimeBoundary(implode($this->parts).implode($this->images));
  1597. $message_body = "Content-Type: multipart/related; ".
  1598. "boundary=\"{$related_boundary}\"\r\n\r\n".
  1599. "--{$related_boundary}\r\n";
  1600. $parts_body = "Content-Type: multipart/alternative; ".
  1601. "boundary=\"{$alternative_boundary}\"\r\n\r\n".
  1602. "--{$alternative_boundary}\r\n".
  1603. implode("\r\n\r\n--$alternative_boundary\r\n", $this->parts).
  1604. "\r\n--$alternative_boundary--\r\n";
  1605. $message_body .= $parts_body.
  1606. "--$related_boundary\r\n";
  1607. $images_body = implode("\r\n\r\n--$related_boundary\r\n", $this->images);
  1608. $message_body .= $images_body.
  1609. "\r\n--$related_boundary--\r\n";
  1610. }
  1611. else
  1612. {
  1613. if (!empty($this->attachments))
  1614. {
  1615. $message_body = "Content-Type: multipart/alternative; ".
  1616. "boundary=\"{$alternative_boundary}\"\r\n\r\n".
  1617. "--{$alternative_boundary}\r\n".
  1618. implode("\r\n\r\n--$alternative_boundary\r\n", $this->parts).
  1619. "\r\n--$alternative_boundary--\r\n";
  1620. }
  1621. else $message_body = implode("\r\n\r\n--$boundary\r\n", $this->parts);
  1622. }
  1623. if (!empty($this->attachments)) //Make a sub-message that contains attachment data
  1624. {
  1625. $mixalt = 'mixed';
  1626. $message_body .= "\r\n\r\n--$boundary\r\n".
  1627. implode("\r\n--$boundary\r\n", $this->attachments);
  1628. }
  1629. $data = "MIME-Version: 1.0\r\n".
  1630. "Content-Type: multipart/{$mixalt};\r\n".
  1631. " boundary=\"{$boundary}\"\r\n".
  1632. "Content-Transfer-Encoding: {$encoding}\r\n\r\n".
  1633. "{$this->mimeWarning}\r\n".
  1634. "--$boundary\r\n".
  1635. "$message_body\r\n".
  1636. "--$boundary--";
  1637. }
  1638. return $data;
  1639. }
  1640. }
  1641. ?>