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