/library/swift/lib/Swift.php
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
- <?php
- /**
- * Swift Mailer: A Flexible PHP Mailer Class.
- *
- * Current functionality:
- *
- * * Send uses one single connection to the SMTP server
- * * Doesn't rely on mail()
- * * Custom Headers
- * * Unlimited redundant connections (can be mixed type)
- * * Connection cycling & load balancing
- * * Sends Multipart messages, handles encoding
- * * Sends Plain-text single-part emails
- * * Fast Cc and Bcc handling
- * * Immune to rejected recipients (sends to subsequent recipients w/out error)
- * * Set Priority Level
- * * Request Read Receipts
- * * Unicode UTF-8 support with auto-detection
- * * Auto-detection of SMTP/Sendmail details based on PHP & server configuration
- * * Batch emailing with multiple To's or without
- * * Support for multiple attachments
- * * Sendmail (or other binary) support
- * * Pluggable SMTP Authentication (LOGIN, PLAIN, MD5-CRAM, POP Before SMTP)
- * * Secure Socket Layer connections (SSL)
- * * Transport Layer security (TLS) - Gmail account holders!
- * * Send mail with inline embedded images easily (or embed other file types)!
- * * Loadable plugin support with event handling features
- *
- * @package Swift
- * @version 2.1.17
- * @author Chris Corbyn
- * @date 18th October 2006
- * @license http://www.gnu.org/licenses/lgpl.txt Lesser GNU Public License
- *
- * @copyright Copyright © 2006 Chris Corbyn - All Rights Reserved.
- * @filesource
- *
- * -----------------------------------------------------------------------
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to
- *
- * The Free Software Foundation, Inc.,
- * 51 Franklin Street,
- * Fifth Floor,
- * Boston,
- * MA 02110-1301 USA
- *
- * "Chris Corbyn" <chris@w3style.co.uk>
- *
- */
- if (!defined('SWIFT_VERSION')) define('SWIFT_VERSION', '2.1.17');
- /**
- * Swift Plugin Interface. Describes the methods which plugins should implement
- * @package Swift
- */
- interface Swift_IPlugin
- {
- /**
- * Required Properties
- *
- * private SwiftInstance;
- * public pluginName;
- */
-
- /**
- * Loads an instance of Swift to the Plugin
- * @param object SwiftInstance
- * @return void
- */
- public function loadBaseObject(&$object);
-
- /**
- * Optional Methods to implement
- *
- * public function onLoad();
- * public function onClose();
- * public function onFail();
- * public function onError();
- * public function onBeforeSend();
- * public function onSend();
- * public function onBeforeCommand();
- * public function onCommand();
- * public function onLog();
- * public function onAuthenticate();
- * public function onFlush();
- * public function onResponse();
- */
- }
- /**
- * Swift Authenticator Interface. Describes the methods which authenticators should implement
- * @package Swift
- */
- interface Swift_IAuthenticator
- {
- /**
- * Required Properties
- * private SwiftInstance;
- * public serverString;
- */
-
- /**
- * Loads an instance of Swift to the Plugin
- * @param object SwiftInstance
- * @return void
- */
- public function loadBaseObject(&$object);
- /**
- * Executes the logic in the authentication mechanism
- * @param string username
- * @param string password
- * @return bool successful
- */
- public function run($username, $password);
-
- }
- /**
- * Swift Connection Handler Interface.
- * Describes the methods which connection handlers should implement
- * @package Swift
- */
- interface Swift_IConnection
- {
- /**
- * Required properties
- *
- * public readHook;
- * public writeHook;
- * public error
- */
-
- /**
- * Establishes a connection with the MTA
- * @return bool connected
- */
- public function start();
- /**
- * Closes the connection with the MTA
- * @return void
- */
- public function stop();
- /**
- * Returns a boolean value TRUE if the connection is active.
- * @return bool connected
- */
- public function isConnected();
- }
- /**
- * Swift Mailer Class.
- * Accepts connections to an MTA and deals with the sending and processing of
- * commands and responses.
- * @package Swift
- */
- class Swift
- {
- /**
- * Plugins container
- * @var array plugins
- * @private
- */
- private $plugins = array();
- private $esmtp = false;
- private $_8bitmime = false;
- private $autoCompliance = false;
- /**
- * Whether or not Swift should send unique emails to all "To"
- * recipients or just bulk them together in the To header.
- * @var bool use_exact
- */
- private $useExactCopy = false;
- private $domain = 'SwiftUser';
- private $mimeBoundary;
- private $mimeWarning;
- /**
- * MIME Parts container
- * @var array parts
- * @private
- */
- private $parts = array();
- /**
- * Attachment data container
- * @var array attachments
- * @private
- */
- private $attachments = array();
- /**
- * Inline image container
- * @var array image parts
- * @private
- */
- private $images = array();
- /**
- * Response codes expected for commands
- * $command => $code
- * @var array codes
- * @private
- */
- private $expectedCodes = array(
- 'ehlo' => 250,
- 'helo' => 250,
- 'mail' => 250,
- 'rcpt' => 250,
- 'data' => 354
- );
- /**
- * Blind-carbon-copy address container
- * @var array addresses
- */
- private $Bcc = array();
- /**
- * Carbon-copy address container
- * @var array addresses
- */
- private $Cc = array();
- /**
- * The address any replies will go to
- * @var string address
- */
- private $replyTo;
- /**
- * The addresses we're sending to
- * @var string address
- */
- private $to = array();
- /**
- * The sender of the email
- * @var string sender
- */
- private $from;
- /**
- * Priority value 1 (high) to 5 (low)
- * @var int priority (1-5)
- */
- private $priority = 3;
- /**
- * Whether a read-receipt is required
- * @var bool read receipt
- */
- private $readReceipt = false;
- /**
- * The max number of entires that can exist in the log
- * (saves memory)
- * @var int log size
- */
- private $maxLogSize = 30;
- /**
- * The address to which bounces are sent
- * @var string Return-Path:
- */
- private $returnPath;
-
- /**
- * Connection object (container holding a socket)
- * @var object connection
- */
- public $connection;
- /**
- * Authenticators container
- * @var array authenticators
- */
- public $authenticators = array();
- public $authTypes = array();
- /**
- * Holds the username used in authentication (if any)
- * @var string username
- */
- public $username;
- /**
- * Holds the password used in authentication (if any)
- * @var string password
- */
- public $password;
-
- public $charset = "ISO-8859-1";
- private $userCharset = false;
- /**
- * Boolean value representing if Swift has failed or not
- * @var bool failed
- */
- public $failed = false;
- /**
- * If Swift should clear headers etc automatically
- * @var bool autoFlush
- */
- public $autoFlush = true;
- /**
- * Numeric code from the last MTA response
- * @var int code
- */
- public $responseCode;
- /**
- * Keyword of the command being sent
- * @var string keyword
- */
- public $commandKeyword;
- /**
- * Last email sent or email about to be sent (dependant on location)
- * @var array commands
- */
- public $currentMail = array();
- /**
- * Email headers
- * @var string headers
- */
- public $headers;
- public $currentCommand = '';
- /**
- * Errors container
- * @var array errors
- */
- public $errors = array();
- /**
- * Log container
- * @var array transactions
- */
- public $transactions = array();
-
- public $lastTransaction;
- public $lastError;
- /**
- * The very most recent response received from the MTA
- * @var string response
- */
- public $lastResponse;
- /**
- * The total number of failed recipients
- * @var int failed
- */
- private $failCount = 0;
- /**
- * Number of failed recipients for this email
- * @var int failed
- */
- private $subFailCount = 0;
- /**
- * Number of addresses expected to pass this email
- * @var int recipients
- */
- private $numAddresses;
- /**
- * Container for any recipients rejected
- * @var array failed addresses
- */
- private $failedAddresses = array();
- /**
- * Number of commands which will be skipped
- */
- public $ignoreCommands = 0;
- /**
- * Number of commands skipped thus far
- */
- private $skippedCommands = 0;
- /**
- * The encoding mode to use in headers (default base64)
- * @var char mode
- */
- private $headerEncoding = 'B';
-
- /**
- * Swift Constructor
- * @param object Swift_IConnection
- * @param string user_domain, optional
- */
- public function __construct(Swift_IConnection &$object, $domain=false)
- {
- if (!$domain) $domain = !empty($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'SwiftUser';
-
- $this->domain = $domain;
- $this->connection =& $object;
- $this->connect();
- // * Hey this library is FREE so it's not much to ask ;) But if you really do want to
- // remove this header then go ahead of course... what's GPL for? :P
- $this->headers = "X-Mailer: Swift ".SWIFT_VERSION." by Chris Corbyn\r\n";
- $this->mimeWarning = "This part of the E-mail should never be seen. If\r\n".
- "you are reading this, consider upgrading your e-mail\r\n".
- "client to a MIME-compatible client.";
- }
- /**
- * Connect to the server
- * @return bool connected
- */
- public function connect()
- {
- if (!$this->connection->start())
- {
- $this->fail();
- $error = 'Connection to the given MTA failed.';
- if (!empty($this->connection->error)) $error .= ' The Connection Interface said: '.$this->connection->error;
- $this->logError($error, 0);
- return false;
- }
- else
- {
- $this->handshake();
- return true;
- }
- }
- /**
- * Returns TRUE if the connection is active.
- */
- public function isConnected()
- {
- return $this->connection->isConnected();
- }
- /**
- * Sends the standard polite greetings to the MTA and then
- * identifies the MTA's capabilities
- */
- public function handshake()
- {
- $this->commandKeyword = "";
- //What did the server greet us with on connect?
- $this->logTransaction();
- if ($this->supportsESMTP($this->lastResponse))
- {
- //Just being polite
- $list = $this->command("EHLO {$this->domain}\r\n");
- $this->check8BitMime($this->lastResponse);
-
- $this->getAuthenticationMethods($list);
-
- $this->esmtp = true;
- }
- else $this->command("HELO {$this->domain}\r\n");
- }
- /**
- * Check if the server allows 8bit emails to be sent without quoted-printable encoding
- * @param string EHLO response
- */
- private function check8BitMime($string)
- {
- if (strpos($string, '8BITMIME')) $this->_8bitmime = true;
- }
- /**
- * Checks for Extended SMTP support
- * @param string MTA greeting
- * @return bool ESMTP
- * @private
- */
- private function supportsESMTP($greeting)
- {
- //Not mentiioned in RFC 2821 but this how it's done
- if (strpos($greeting, 'ESMTP')) return true;
- else return false;
- }
- /**
- * Set the maximum num ber of entries in the log
- * @param int size
- */
- public function setMaxLogSize($size)
- {
- $this->maxLogSize = (int) $size;
- }
- /**
- * Sets the priority level of the email
- * This must be 1 to 5 where 1 is highest
- * @param int priority
- */
- public function setPriority($level)
- {
- $level = (int) $level;
- if ($level < 1) $level = 1;
- if ($level > 5) $level = 5;
- switch ($level)
- {
- case 1: case 2:
- $this->addHeaders("X-Priority: $level\r\nX-MSMail-Priority: High");
- break;
- case 4: case 5:
- $this->addHeaders("X-Priority: $level\r\nX-MSMail-Priority: Low");
- break;
- case 3: default:
- $this->addHeaders("X-Priority: $level\r\nX-MSMail-Priority: Normal");
- }
- }
- /**
- * Set the encoding to use in headers
- * @param string mode
- */
- public function setHeaderEncoding($mode='B')
- {
- switch (strtoupper($mode))
- {
- case 'Q':
- case 'QP':
- case 'QUOTED-PRINTABLE':
- $this->headerEncoding = 'Q';
- break;
- default:
- $this->headerEncoding = 'B';
- }
- }
- /**
- * Set the return path address (Bounce detection)
- * @param string address
- */
- public function setReturnPath($address)
- {
- $this->returnPath = $this->getAddress($address);
- }
- /**
- * Request a read receipt from all recipients
- * @param bool request receipt
- */
- public function requestReadReceipt($request=true)
- {
- $this->readReceipt = (bool) $request;
- }
- /**
- * Set the character encoding were using
- * @param string charset
- */
- public function setCharset($string)
- {
- $this->charset = $string;
- $this->userCharset = $string;
- }
- /**
- * Whether or not Swift should send unique emails to all To recipients
- * @param bool unique
- */
- public function useExactCopy($use=true)
- {
- $this->useExactCopy = (bool) $use;
- }
- /**
- * Get the return path recipient
- */
- public function getReturnPath()
- {
- return $this->returnPath;
- }
- /**
- * Get the sender
- */
- public function getFromAddress()
- {
- return $this->from;
- }
- /**
- * Get Cc recipients
- */
- public function getCcAddresses()
- {
- return $this->Cc;
- }
- /**
- * Get Bcc addresses
- */
- public function getBccAddresses()
- {
- return $this->Bcc;
- }
- /**
- * Get To addresses
- */
- public function getToAddresses()
- {
- return $this->to;
- }
- /**
- * Get the list of failed recipients
- * @return array recipients
- */
- public function getFailedRecipients()
- {
- return $this->failedAddresses;
- }
- /**
- * Return the array of errors (if any)
- * @return array errors
- */
- public function getErrors()
- {
- return $this->errors;
- }
- /**
- * Return the conversation up to maxLogSize between the SMTP server and swift
- * @return array transactions
- */
- public function getTransactions()
- {
- return $this->transactions;
- }
- /**
- * Sets the Reply-To address used for sending mail
- * @param string address
- */
- public function setReplyTo($string)
- {
- $this->replyTo = $this->getAddress($string);
- }
- /**
- * Add one or more Blind-carbon-copy recipients to the mail
- * @param mixed addresses
- */
- public function addBcc($addresses)
- {
- $this->Bcc = array_merge($this->Bcc, $this->parseAddressList((array) $addresses));
- }
- /**
- * Add one or more Carbon-copy recipients to the mail
- * @param mixed addresses
- */
- public function addCc($addresses)
- {
- $this->Cc = array_merge($this->Cc, $this->parseAddressList((array) $addresses));
- }
- /**
- * Force swift to break lines longer than 76 characters long
- * @param bool resize
- */
- public function useAutoLineResizing($use=true)
- {
- $this->autoCompliance = (bool) $use;
- }
- /**
- * Associate a code with a command. Swift will fail quietly if the code
- * returned does not match.
- * @param string command
- * @param int code
- */
- public function addExpectedCode($command, $code)
- {
- $this->expectedCodes[$command] = (int) $code;
- }
- /**
- * Reads the EHLO return string to see what AUTH methods are supported
- * @param string EHLO response
- * @return void
- * @private
- */
- private function getAuthenticationMethods($list)
- {
- preg_match("/^250[\-\ ]AUTH\ (.*)\r\n/m", $list, $matches);
- if (!empty($matches[1]))
- {
- $types = explode(' ', $matches[1]);
- $this->authTypes = $types;
- }
- }
- /**
- * Load a plugin object into Swift
- * @param object Swift_IPlugin
- * @param string plugin name
- * @return void
- */
- public function loadPlugin(Swift_IPlugin &$object, $id=false)
- {
- if ($id) $object->pluginName = $id;
- $this->plugins[$object->pluginName] =& $object;
- $this->plugins[$object->pluginName]->loadBaseObject($this);
- if (method_exists($this->plugins[$object->pluginName], 'onLoad'))
- {
- $this->plugins[$object->pluginName]->onLoad();
- }
- }
- /**
- * Fetch a reference to a plugin in Swift
- * @param string plugin name
- * @return object Swift_IPlugin
- */
- public function &getPlugin($name)
- {
- if (isset($this->plugins[$name]))
- {
- return $this->plugins[$name];
- }
- }
- /**
- * Un-plug a loaded plugin. Returns false on failure.
- * @param string plugin_name
- * @return bool success
- */
- public function removePlugin($name)
- {
- if (!isset($this->plugins[$name])) return false;
-
- if (method_exists($this->plugins[$name], 'onUnload'))
- {
- $this->plugins[$name]->onUnload();
- }
- unset($this->plugins[$name]);
- return true;
- }
- /**
- * Return the number of plugins loaded
- * @return int plugins
- */
- public function numPlugins()
- {
- return count($this->plugins);
- }
- /**
- * Trigger event handlers
- * @param string event handler
- * @return void
- * @private
- */
- private function triggerEventHandler($func)
- {
- foreach ($this->plugins as $name => $object)
- {
- if (method_exists($this->plugins[$name], $func))
- {
- $this->plugins[$name]->$func();
- }
- }
- }
- /**
- * Attempt to load any authenticators from the Swift/ directory
- * @see RFC 2554
- * @return void
- * @private
- */
- private function loadDefaultAuthenticators()
- {
- $dir = dirname(__FILE__).'/Swift/Authenticator';
- if (file_exists($dir) && is_dir($dir))
- {
- $handle = opendir($dir);
- while ($file = readdir($handle))
- {
- if (preg_match('@^([a-zA-Z\d]*)\.php$@', $file, $matches))
- {
- require_once($dir.'/'.$file);
- $class = 'Swift_Authenticator_'.$matches[1];
- $this->loadAuthenticator(new $class);
- }
- }
- closedir($handle);
- }
- }
- /**
- * Use SMTP authentication
- * @param string username
- * @param string password
- * @return bool successful
- */
- public function authenticate($username, $password)
- {
- $this->username = $username;
- $this->password = $password;
-
- if (empty($this->authenticators)) $this->loadDefaultAuthenticators();
-
- if (!$this->esmtp || empty($this->authTypes))
- {
- $this->logError('The MTA doesn\'t support any of Swift\'s loaded authentication mechanisms', 0);
- return false;
- }
- foreach ($this->authenticators as $name => $object)
- {
- //An asterisk means that the auth type is not advertised by ESMTP
- if (in_array($name, $this->authTypes) || substr($name, 0, 1) == '*')
- {
- if ($this->authenticators[$name]->run($username, $password))
- {
- $this->triggerEventHandler('onAuthenticate');
- return true;
- }
- else return false;
- }
- }
- //If we get this far, no authenticators were used
- $this->logError('The MTA doesn\'t support any of Swift\'s loaded authentication mechanisms', 0);
- $this->fail();
- return false;
- }
- /**
- * Load an authentication mechanism object into Swift
- * @param object Swift_IAuthenticator
- * @return void
- */
- public function loadAuthenticator(Swift_IAuthenticator &$object)
- {
- $this->authenticators[$object->serverString] =& $object;
- $this->authenticators[$object->serverString]->loadBaseObject($this);
- }
- /**
- * Get a unique multipart MIME boundary
- * @param string mail data, optional
- * @return string boundary
- * @private
- */
- private function getMimeBoundary($string=false)
- {
- $force = true;
- if (!$string)
- {
- $force = false;
- $string = implode('', $this->parts);
- $string .= implode('', $this->attachments);
- }
- if ($this->mimeBoundary && !$force) return $this->mimeBoundary;
- else
- { //Make sure we don't (as if it would ever happen!) -
- // produce a boundary that's actually in the email already
- do
- {
- $this->mimeBoundary = '_=_swift-'.uniqid(rand(), true);
- } while(strpos($string, $this->mimeBoundary));
- }
- return $this->mimeBoundary;
- }
- /**
- * Append a string to the message header
- * @param string headers
- * @return void
- */
- public function addHeaders($string)
- {
- $this->headers .= preg_replace("/(?:\r|\n|^)[^:]*?:\ *(.*?)(?:\r|\n|$)/me", 'str_replace("$1", $this->safeEncodeHeader("$1"), "$0")', $string);
- if (substr($this->headers, -2) != "\r\n")
- $this->headers .= "\r\n";
- }
- /**
- * Set the multipart MIME boundary (only works for first part)
- * @param string boundary
- * @return void
- */
- public function setMimeBoundary($string)
- {
- $this->mimeBoundary = $string;
- }
- /**
- * Set the text that displays in non-MIME clients
- * @param string warning
- * @return void
- */
- public function setMimeWarning($warning)
- {
- $this->mimeWarning = $warning;
- }
- /**
- * Tells Swift to clear out attachment, parts, headers etc
- * automatically upon sending - this is the default.
- * @param bool flush
- */
- public function autoFlush($flush=true)
- {
- $this->autoFlush = (bool) $flush;
- }
- /**
- * Empty out the MIME parts and attachments
- * @param bool reset headers
- * @return void
- */
- public function flush($clear_headers=false)
- {
- $this->parts = array();
- $this->attachments = array();
- $this->images = array();
- $this->mimeBoundary = null;
- $this->Bcc = array();
- $this->to = array();
- $this->Cc = array();
- $this->replyTo = null;
- //See comment above the headers property above the constructor before editing this line! *
- if ($clear_headers) $this->headers = "X-Mailer: Swift ".SWIFT_VERSION." by Chris Corbyn\r\n";
- $this->triggerEventHandler('onFlush');
- }
- /**
- * Reset to
- */
- public function flushTo()
- {
- $this->to = array();
- }
- /**
- * Reset Cc
- */
- public function flushCc()
- {
- $this->Cc = array();
- }
- /**
- * Reset Bcc
- */
- public function flushBcc()
- {
- $this->Bcc = array();
- }
- /**
- * Reset parts
- */
- public function flushParts()
- {
- $this->parts = array();
- $this->images = array();
- }
- /**
- * Reset attachments
- */
- public function flushAttachments()
- {
- $this->attachments = array();
- }
- /**
- * Reset headers
- */
- public function flushHeaders()
- {
- $this->headers = "X-Mailer: Swift ".SWIFT_VERSION." by Chris Corbyn\r\n";
- }
- /**
- * Log an error in Swift::errors
- * @param string error string
- * @param int error number
- * @return void
- */
- public function logError($errstr, $errno=0)
- {
- $this->errors[] = array(
- 'num' => $errno,
- 'time' => microtime(),
- 'message' => $errstr
- );
- $this->lastError = $errstr;
-
- $this->triggerEventHandler('onError');
- }
- /**
- * Log a transaction in Swift::transactions
- * @param string command
- * @return void
- */
- public function logTransaction($command='')
- {
- $this->lastTransaction = array(
- 'command' => $command,
- 'time' => microtime(),
- 'response' => $this->getResponse()
- );
- $this->triggerEventHandler('onLog');
- if ($this->maxLogSize)
- {
- $this->transactions = array_slice(array_merge($this->transactions, array($this->lastTransaction)), -$this->maxLogSize);
- }
- else $this->transactions[] = $this->lastTransaction;
- }
- /**
- * Read the data from the socket
- * @return string response
- * @private
- */
- private function getResponse()
- {
- if (!$this->connection->readHook || !$this->isConnected()) return false;
- $ret = "";
- while (true)
- {
- $tmp = @fgets($this->connection->readHook);
- $ret .= $tmp;
- //The last line of SMTP replies have a space after the status number
- // They do NOT have an EOF so while(!feof($socket)) will hang!
- if (substr($tmp, 3, 1) == ' ' || $tmp == false) break;
- }
- $this->responseCode = $this->getResponseCode($ret);
- $this->lastResponse = $ret;
- $this->triggerEventHandler('onResponse');
- return $this->lastResponse;
- }
- /**
- * Get the number of the last server response
- * @param string response string
- * @return int response code
- * @private
- */
- private function getResponseCode($string)
- {
- return (int) sprintf("%d", $string);
- }
- /**
- * Get the first word of the command
- * @param string command
- * @return string keyword
- * @private
- */
- private function getCommandKeyword($comm)
- {
- if (false !== $pos = strpos($comm, ' '))
- {
- return $this->commandKeyword = strtolower(substr($comm, 0, $pos));
- }
- else return $this->commandKeyword = strtolower(trim($comm));
- }
- /**
- * Send a reset command in the event of a problem
- */
- public function reset()
- {
- $this->command("RSET\r\n");
- }
- /**
- * Issue a command to the socket
- * @param string command
- * @return string response
- */
- public function command($comm)
- {
- //We'll usually ignore a certain sequence of commands if something screwed up
- if ($this->ignoreCommands)
- {
- $this->skippedCommands++;
- if ($this->skippedCommands >= $this->ignoreCommands)
- {
- $this->responseCode = -2; //Done (internal to swift)
- $this->ignoreCommands = 0;
- $this->skippedCommands = 0;
- }
- return true;
- }
-
- $this->currentCommand = ltrim($comm);
-
- $this->triggerEventHandler('onBeforeCommand');
-
- if (!$this->connection->writeHook || !$this->isConnected() || $this->failed)
- {
- $this->logError('Error running command: '.trim($comm).'. No connection available', 0);
- return false;
- }
- $command_keyword = $this->getCommandKeyword($this->currentCommand);
-
- //We successfully got as far as asking to send the email so we can forget any failed addresses for now
- if ($command_keyword != 'rcpt' && $command_keyword != 'rset') $this->subFailCount = 0;
-
- //SMTP commands must end with CRLF
- if (substr($this->currentCommand, -2) != "\r\n") $this->currentCommand .= "\r\n";
-
- if (@fwrite($this->connection->writeHook, $this->currentCommand))
- {
- $this->logTransaction($this->currentCommand);
- if (array_key_exists($command_keyword, $this->expectedCodes))
- {
- if ($this->expectedCodes[$command_keyword] != $this->responseCode)
- {
- //If a recipient was rejected
- if ($command_keyword == 'rcpt')
- {
- $this->failCount++;
- $this->failedAddresses[] = $this->getAddress($comm);
- //Some addresses may still work...
- if (++$this->subFailCount >= $this->numAddresses)
- {
- //Sending failed, just RSET and don't send data to this recipient
- $this->reset();
- //So we can still cache the mail body in send()
- $this->responseCode = -1; //Pending (internal to swift)
- //Skip the next two commands (DATA and <mail>)
- $this->ignoreCommands = 2;
- $this->logError('Send Error: Sending to '.$this->subFailCount.' recipients rejected (bad response code).', $this->responseCode);
- //But don't fail here.... these are usually not fatal
- }
- }
- else
- {
- $this->fail();
- $this->logError('MTA Error (Swift was expecting response code '.$this->expectedCodes[$command_keyword].' but got '.$this->responseCode.'): '.$this->lastResponse, $this->responseCode);
- return $this->hasFailed();
- }
- }
- }
- $this->triggerEventHandler('onCommand');
- return $this->lastResponse;
- }
- else return false;
- }
- /**
- * Splits lines longer than 76 characters to multiple lines
- * @param string text
- * @return string chunked output
- */
- public function chunkSplitLines($string)
- {
- return wordwrap($string, 74, "\r\n");
- }
- /**
- * Add a part to a multipart message
- * @param string body
- * @param string content-type, optional
- * @param string content-transfer-encoding, optional
- * @return void
- */
- public function addPart($string, $type='text/plain', $encoding=false)
- {
- if (!$this->userCharset && (strtoupper($this->charset) != 'UTF-8') && $this->detectUTF8($string)) $this->charset = 'UTF-8';
-
- if (!$encoding && $this->_8bitmime) $encoding = '8bit';
- elseif (!$encoding) $encoding = 'quoted-printable';
-
- $body_string = $this->encode($string, $encoding);
- if ($this->autoCompliance && $encoding != 'binary') $body_string = $this->chunkSplitLines($body_string);
- $ret = "Content-Type: $type; charset=\"{$this->charset}\"; format=flowed\r\n".
- "Content-Transfer-Encoding: $encoding\r\n\r\n".
- $body_string;
-
- if (strtolower($type) == 'text/html') $this->parts[] = $this->makeSafe($ret);
- else $this->parts = array_merge((array) $this->makeSafe($ret), $this->parts);
- }
- /**
- * Get the current number of parts in the email
- * @return int num parts
- */
- public function numParts()
- {
- return count($this->parts);
- }
- /**
- * Add an attachment to a multipart message.
- * Attachments are added as base64 encoded data.
- * @param string data
- * @param string filename
- * @param string content-type
- * @return void
- */
- public function addAttachment($data, $filename, $type='application/octet-stream')
- {
- $this->attachments[] = "Content-Type: $type; ".
- "name=\"$filename\";\r\n".
- "Content-Transfer-Encoding: base64\r\n".
- "Content-Description: $filename\r\n".
- "Content-Disposition: attachment; ".
- "filename=\"$filename\"\r\n\r\n".
- chunk_split($this->encode($data, 'base64'));
- }
- /**
- * Get the current number of attachments in the mail
- * @return int num attachments
- */
- public function numAttachments()
- {
- return count($this->attachments);
- }
- /**
- * Insert an inline image and return it's name
- * These work like attachments but have a content-id
- * and are inline/related.
- * @param string path
- * @return string name
- */
- public function addImage($path)
- {
- if (!file_exists($path)) return false;
-
- $gpc = ini_get('magic_quotes_gpc');
- ini_set('magic_quotes_gpc', 0);
- $gpc_run = ini_get('magic_quotes_runtime');
- ini_set('magic_quotes_runtime', 0);
-
- $img_data = @getimagesize($path);
- if (!$img_data) return false;
-
- $type = image_type_to_mime_type($img_data[2]);
- $filename = basename($path);
- $data = file_get_contents($path);
- $cid = 'SWM'.md5(uniqid(rand(), true));
-
- $this->images[] = "Content-Type: $type\r\n".
- "Content-Transfer-Encoding: base64\r\n".
- "Content-Disposition: inline; ".
- "filename=\"$filename\"\r\n".
- "Content-ID: <$cid>\r\n\r\n".
- chunk_split($this->encode($data, 'base64'));
-
- ini_set('magic_quotes_gpc', $gpc);
- ini_set('magic_quotes_runtime', $gpc_run);
-
- return 'cid:'.$cid;
- }
- /**
- * Insert an inline file and return it's name
- * These work like attachments but have a content-id
- * and are inline/related.
- * The data is the file contents itself (binary safe)
- * @param string file contents
- * @param string content-type
- * @param string filename
- * @param string content-id
- * @return string name
- */
- public function embedFile($data, $type='application/octet-stream', $filename=false, $cid=false)
- {
- if (!$cid) $cid = 'SWM'.md5(uniqid(rand(), true));
-
- if (!$filename) $filename = $cid;
-
- $this->images[] = "Content-Type: $type\r\n".
- "Content-Transfer-Encoding: base64\r\n".
- "Content-Disposition: inline; ".
- "filename=\"$filename\"\r\n".
- "Content-ID: <$cid>\r\n\r\n".
- chunk_split($this->encode($data, 'base64'));
-
- return 'cid:'.$cid;
- }
- /**
- * Close the connection in the connecion object
- * @return void
- */
- public function close()
- {
- if ($this->connection->writeHook && $this->isConnected())
- {
- $this->command("QUIT\r\n");
- $this->connection->stop();
- }
- $this->triggerEventHandler('onClose');
- }
- /**
- * Check if Swift has failed and stopped processing
- * @return bool failed
- */
- public function hasFailed()
- {
- return $this->failed;
- }
- /**
- * Force Swift to fail and stop processing
- * @return void
- */
- public function fail()
- {
- $this->failed = true;
- $this->triggerEventHandler('onFail');
- }
- /**
- * Detect if a string contains multi-byte non-ascii chars that fall in the UTF-8 tanges
- * @param mixed input
- * @return bool
- */
- public function detectUTF8($string_in)
- {
- foreach ((array)$string_in as $string)
- {
- if (is_array($string))
- {
- if ($this->detectUTF8($string)) return true;
- }
- elseif (preg_match('%(?:
- [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
- |\xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
- |[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
- |\xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
- |\xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
- |[\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
- |\xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
- )+%xs', $string)) return true;
- }
- return false;
- }
- /**
- * This function checks for 7bit *printable* characters
- * which excludes \r \n \t etc and so, is safe for use in mail headers
- * Actual permitted chars [\ !"#\$%&'\(\)\*\+,-\.\/0123456789:;<=>\?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]\^_`abcdefghijklmnopqrstuvwxyz{\|}~]
- * Ranges \x00-\x1F are printer control sequences
- * \x7F is the ascii delete character
- * @param string input
- * @return bool
- */
- public function is7bitPrintable($string)
- {
- if (preg_match('/^[\x20-\x7E]*$/D', $string)) return true;
- else return false;
- }
- /**
- * This is harsh! It makes things safe for sending as command sequences in SMTP
- * Specifically the enveloping. We could be extremely strict and implement what I planned on doing
- * here: http://www.swiftmailer.org/contrib/proposed-address-test.txt
- * @param string input
- * @return safe output
- */
- public function make7bitPrintable($string)
- {
- return preg_replace('/[^\x20-\x7E]/', '', $string);
- }
- /**
- * Encode a string (mail) in a given format
- * Currently supports:
- * - BASE64
- * - Quoted-Printable
- * - Ascii 7-bit
- * - Ascii 8bit
- * - Binary (not encoded)
- *
- * @param string input
- * @param string encoding
- * @return string encoded output
- */
- public function encode($string, $type, $maxlen=false)
- {
- $type = strtolower($type);
-
- switch ($type)
- {
- case 'base64':
- return base64_encode($string);
- break;
- //
- case 'quoted-printable':
- return $this->quotedPrintableEncode($string, $maxlen);
- //
- case '7bit':
- case '8bit':
- break;
- case 'binary':
- default:
- break;
- }
-
- return $string;
- }
- /**
- * Headers cannot have non-ascii or high ascii chars in them
- * @param mixed input
- * @return string encoded output (with encoding type)
- */
- public function safeEncodeHeader($string)
- {
- if (!is_array($string))
- {
- if ($this->is7bitPrintable($string)) return $string;
- else
- {
- //Check if the string contains address notation
- $address_start = strrpos($string, '<');
- $address_end = strrpos($string, '>');
- $address = '';
- //If the < and > are in the correct places
- if (($address_start !== false) && $address_start < $address_end)
- {
- //Then store the email address
- $address = substr($string, $address_start, ($address_end-$address_start+1));
- if (!$this->is7bitPrintable($address)) $address = $this->make7bitPrintable($address);
- //... and remove it from the string
- $string = substr($string, 0, $address_start);
- }
-
- if ($this->headerEncoding == 'B') $encoded = trim(chunk_split($this->encode($string, 'base64')));
- else
- {
- $this->headerEncoding = 'Q';
- $encoded = trim($this->encode($string, 'quoted-printable'));
- }
- $lines = explode("\r\n", $encoded);
- return '=?'.$this->charset.'?'.$this->headerEncoding.'?'.implode("?=\r\n =?{$this->charset}?{$this->headerEncoding}?", $lines).'?= '.$address;
- }
- }
- else
- {
- $ret = array();
- foreach ($string as $line)
- {
- $ret[] = $this->safeEncodeHeader($line); //Recurse
- }
- return $ret;
- }
- }
- /**
- * Handles quoted-printable encoding
- * From php.net by user bendi at interia dot pl
- * @param string input
- * @param int maxlength
- * @return string encoded output
- * @private
- */
- private function quotedPrintableEncode($string, $maxlen=false)
- {
- if (!$maxlen) $maxlen = 73;
- $string = preg_replace('/[^\x21-\x3C\x3E-\x7E\x09\x20]/e', 'sprintf( "=%02x", ord ( "$0" ) ) ;', $string);
- preg_match_all('/.{1,'.$maxlen.'}([^=]{0,3})?/', $string, $matches);
- $sep = "=\r\n";
- return implode($sep, $matches[0]);
- }
- /**
- * Converts lone LF characters to CRLF
- * @param string input
- * @return string converted output
- */
- public function LFtoCRLF($string)
- {
- return preg_replace("@(?:(?<!\r)\n)|(?:\r(?!\n))@", "\r\n", $string);
- }
- /**
- * Prevents premature <CRLF>.<CRLF> strings
- * Converts any lone LF characters to CRLF
- * @param string input
- * @return string escaped output
- */
- public function makeSafe($string)
- {
- return str_replace("\r\n.", "\r\n..", $this->LFtoCRLF($string));
- }
- /**
- * Pulls an email address from a "Name" <add@ress> string
- * @param string input
- * @return string address
- */
- public function getAddress($string)
- {
- if (!$string) return null;
-
- if (preg_match('/^.*?<([^>]+)>\s*$/s', $string, $matches))
- {
- return '<'.$this->make7bitPrintable($matches[1]).'>';
- }
- elseif (!preg_match('/<|>/', $string)) return '<'.$this->make7bitPrintable($string).'>';
- else return $this->make7bitPrintable($string);
- }
- /**
- * Builds the headers needed to reflect who the mail is sent to
- * Presently this is just the "To: " header
- * @param string address
- * @return string headers
- * @private
- */
- private function makeRecipientHeaders($address=false)
- {
- if ($address) return "To: ".$this->safeEncodeHeader($address)."\r\n";
- else
- {
- $ret = "To: ".implode(",\r\n\t", $this->safeEncodeHeader($this->to))."\r\n";
- if (!empty($this->Cc)) $ret .= "Cc: ".implode(",\r\n\t", $this->safeEncodeHeader($this->Cc))."\r\n";
- return $ret;
- }
- }
- /**
- * Structure a given array of addresses into the 1-dim we want
- * @param array unstructured
- * @return array structured
- * @private
- */
- private function parseAddressList($u_array)
- {
- $ret = array();
- foreach ($u_array as $val)
- {
- if (is_array($val)) $ret[] = '"'.$val[0].'" <'.$val[1].'>';
- else $ret[] = $val;
- }
- return $ret;
- }
- /**
- * Send an email using Swift (send commands)
- * @param string to_address
- * @param string from_address
- * @param string subject
- * @param string body, optional
- * @param string content-type,optional
- * @param string content-transfer-encoding,optional
- * @return bool successful
- */
- public function send($to, $from, $subject, $body=false, $type='text/plain', $encoding=false)
- {
- if ((strtoupper($this->charset) != 'UTF-8') && $body && $this->detectUTF8($body) && !$this->userCharset) $this->charset = 'UTF-8';
- if ((strtoupper($this->charset) != 'UTF-8') && $this->detectUTF8($subject) && !$this->userCharset) $this->charset = 'UTF-8';
- if ((strtoupper($this->charset) != 'UTF-8') && $this->detectUTF8($to) && !$this->userCharset) $this->charset = 'UTF-8';
- if ((strtoupper($this->charset) != 'UTF-8') && $this->detectUTF8($from) && !$this->userCharset) $this->charset = 'UTF-8';
-
- if (!$encoding && $this->_8bitmime) $encoding = '8bit';
- elseif (!$encoding) $encoding = 'quoted-printable';
-
- $to = (array) $to;
- $this->to = $this->parseAddressList($to);
- //In these cases we just send the one email
- if ($this->useExactCopy || !empty($this->Cc) || !empty($this->Bcc))
- {
- $this->currentMail = $this->buildMail(false, $from, $subject, $body, $type, $encoding, 1);
- $this->triggerEventHandler('onBeforeSend');
- foreach ($this->currentMail as $command)
- {
- //Number of successful addresses expected
- $this->numAddresses = 1;
-
- if (is_array($command))
- { //Commands can be returned as 1-dimensional arrays
- $this->numAddresses = count($command);
- foreach ($command as $c)
- {
- if (!$this->command($c))
- {
- $this->logError('Sending failed on command: '.$c, 0);
- return false;
- }
- }
- }
- else if (!$this->command($command))
- {
- $this->logError('Sending failed on command: '.$command, 0);
- return false;
- }
- }
- $this->triggerEventHandler('onSend');
- }
- else
- {
- $get_body = true;
- $cached_body = '';
- foreach ($this->to as $address)
- {
- $this->currentMail = $this->buildMail($address, $from, $subject, $body, $type, $encoding, $get_body);
- //If we have a cached version
- if (!$get_body) $this->currentMail[] = $this->makeRecipientHeaders($address).$cached_body;
- $this->triggerEventHandler('onBeforeSend');
- foreach ($this->currentMail as $command)
- {
- //This means we're about to send the DATA part
- if ($get_body && ($this->responseCode == 354 || $this->responseCode == -1))
- {
- $cached_body = $command;
- $command = $this->makeRecipientHeaders($address).$command;
- }
- if (is_array($command))
- {
- foreach ($command as $c)
- {
- if (!$this->command($c))
- {
- $this->logError('Sending failed on command: '.$c, 0);
- return false;
- }
- }
- }
- else if (!$this->command($command))
- {
- $this->logError('Sending failed on command: '.$command, 0);
- return false;
- }
- }
- $this->triggerEventHandler('onSend');
- $get_body = false;
- }
- }
- if ($this->autoFlush) $this->flush(true); //Tidy up a bit
- return true;
- }
- /**
- * Builds the list of commands to send the email
- * The last command in the output is the email itself (DATA)
- * The commands are as follows:
- * - MAIL FROM: <address> (0)
- * - RCPT TO: <address> (1)
- * - DATA (2)
- * - <email> (3)
- *
- * @param string to_address
- * @param string from_address
- * @param string subject
- * @param string body, optional
- * @param string content-type, optional
- * @param string encoding, optional
- * @return array commands
- * @private
- */
- private function buildMail($to, $from, $subject, $body, $type='text/plain', $encoding='8bit', $return_data_part=true)
- {
- $date = date('r'); //RFC 2822 date
- $return_path = $this->returnPath ? $this->returnPath : $this->getAddress($from);
- $ret = array("MAIL FROM: ".$return_path."\r\n"); //Always
- //If the user specifies a different reply-to
- $reply_to = !empty($this->replyTo) ? $this->getAddress($this->replyTo) : $this->getAddress($from);
- //Standard headers
- $this->from = $from;
- $data = "From: ".$this->safeEncodeHeader($from)."\r\n".
- "Reply-To: ".$this->safeEncodeHeader($reply_to)."\r\n".
- "Subject: ".$this->safeEncodeHeader($subject)."\r\n".
- "Date: $date\r\n";
- if ($this->readReceipt) $data .= "Disposition-Notification-To: ".$this->safeEncodeHeader($from)."\r\n";
-
- if (!$to) //Only need one mail if no address was given
- { //We'll collate the addresses from the class properties
- $data .= $this->getMimeBody($body, $type, $encoding)."\r\n.\r\n";
- $headers = $this->makeRecipientHeaders();
- //Rcpt can be run several times
- $rcpt = array();
- foreach ($this->to as $address) $rcpt[] = "RCPT TO: ".$this->getAddress($address)."\r\n";
- foreach ($this->Cc as $address) $rcpt[] = "RCPT TO: ".$this->getAddress($address)."\r\n";
- $ret[] = $rcpt;
- $ret[] = "DATA\r\n";
- $ret[] = $headers.$this->headers.$data;
- //Bcc recipients get to see their own Bcc header but nobody else's
- foreach ($this->Bcc as $address)
- {
- $ret[] = "MAIL FROM: ".$this->getAddress($from)."\r\n";
- $ret[] = "RCPT TO: ".$this->getAddress($address)."\r\n";
- $ret[] = "DATA\r\n";
- $ret[] = $headers."Bcc: ".$this->safeEncodeHeader($address)."\r\n".$this->headers.$data;
- }
- }
- else //Just make this individual email
- {
- if ($return_data_part) $mail_body = $this->getMimeBody($body, $type, $encoding);
- $ret[] = "RCPT TO: ".$this->getAddress($to)."\r\n";
- $ret[] = "DATA\r\n";
- if ($return_data_part) $ret[] = $data.$this->headers.$mail_body."\r\n.\r\n";
- }
- return $ret;
- }
- /**
- * Returns the MIME-specific headers followed by the email
- * content as a string.
- * @param string body
- * @param string content-type
- * @param string encoding
- * @return string mime data
- * @private
- */
- private function getMimeBody($string, $type, $encoding)
- {
- if ($string) //Not using MIME parts
- {
- $body = $this->encode($string, $encoding);
- if ($this->autoCompliance) $body = $this->chunkSplitLines($body);
- $data = "Content-Type: $type; charset=\"{$this->charset}\"; format=flowed\r\n".
- "Content-Transfer-Encoding: $encoding\r\n\r\n".
- $this->makeSafe($body);
- }
- else
- { //Build a full email from the parts we have
- $boundary = $this->getMimeBoundary();
- $encoding = '8bit';
- $mixalt = 'alternative';
- $alternative_boundary = $this->getMimeBoundary(implode($this->parts));
- if (!empty($this->images))
- {
- $mixalt = 'mixed';
- $related_boundary = $this->getMimeBoundary(implode($this->parts).implode($this->images));
-
- $message_body = "Content-Type: multipart/related; ".
- "boundary=\"{$related_boundary}\"\r\n\r\n".
- "--{$related_boundary}\r\n";
-
- $parts_body = "Content-Type: multipart/alternative; ".
- "boundary=\"{$alternative_boundary}\"\r\n\r\n".
- "--{$alternative_boundary}\r\n".
- implode("\r\n\r\n--$alternative_boundary\r\n", $this->parts).
- "\r\n--$alternative_boundary--\r\n";
-
- $message_body .= $parts_body.
- "--$related_boundary\r\n";
-
- $images_body = implode("\r\n\r\n--$related_boundary\r\n", $this->images);
-
- $message_body .= $images_body.
- "\r\n--$related_boundary--\r\n";
-
- }
- else
- {
- if (!empty($this->attachments))
- {
- $message_body = "Content-Type: multipart/alternative; ".
- "boundary=\"{$alternative_boundary}\"\r\n\r\n".
- "--{$alternative_boundary}\r\n".
- implode("\r\n\r\n--$alternative_boundary\r\n", $this->parts).
- "\r\n--$alternative_boundary--\r\n";
- }
- else $message_body = implode("\r\n\r\n--$boundary\r\n", $this->parts);
- }
-
- if (!empty($this->attachments)) //Make a sub-message that contains attachment data
- {
- $mixalt = 'mixed';
- $message_body .= "\r\n\r\n--$boundary\r\n".
- implode("\r\n--$boundary\r\n", $this->attachments);
- }
-
- $data = "MIME-Version: 1.0\r\n".
- "Content-Type: multipart/{$mixalt};\r\n".
- " boundary=\"{$boundary}\"\r\n".
- "Content-Transfer-Encoding: {$encoding}\r\n\r\n".
- "{$this->mimeWarning}\r\n".
- "--$boundary\r\n".
- "$message_body\r\n".
- "--$boundary--";
- }
- return $data;
- }
- }
- ?>