
Official website:
http://www.swiftmailer.org/
Online Documentation:
http://www.swiftmailer.org/documentation/
Bugs/Tracking:
http://sourceforge.net/tracker/?group_id=170045
Donations:
http://sourceforge.net/donate/index.php?group_id=170045
The SourceForge link to PayPal sometimes doesn't work so the PayPal address is chris@w3style.co.uk
(Donations are currently being used so I can get a new Mac. Total so far is £305 (17th Nov 2006) of £400 needed).
You are advised to download one of the archives from the website (or preferably sourceforge) rather than directly copying and pasting the code.
PHP5: [GZIP] Swift-2.1.17.tar.gz
PHP5: [ZIP] Swift-2.1.17.zip
PHP4: [GZIP] Swift-2.1.17-php4.tar.gz
PHP4: [ZIP] Swift-2.1.17-php4.zip
SourceForge Releases: http://sourceforge.net/project/showfile ... _id=170045
-----------------------------------------------------------------
This is a mailer class that uses sockets and avoids the torturous use of the mail() function.
Features:
- Connect to an SMTP Server or use a binary on the local machine
- Send Batch emails over a single connection (tested on my own low-spec PC, low-bandwidth connection to remote SMTP to send over 500 emails per minute)
- Create multipart emails with MIME 1.0 support
- Add attachments (even dynamically generated ones)
- Use SMTP authentication (pluggable -- LOGIN, PLAIN, CRAM-MD5, POP3 Before SMTP)
- Add custom headers
- Multiple redundant connections and/or connection rotation
- Fast Cc and Bcc handling
- Unicode UTF-8 support, with auto detection
- Batch emailing with multiple To's or without
- Continues sending to batch when any recipients fail (other mailer classes stop when an address fails)
- Send to thousands of recipients without cron
- Request Read Receipts
- Set Message Priority
- Send inline images or other file types
- TLS (STARTTLS) Encryption (used by Gmail servers)
- Keep logs of all commands and responses with the server
- Use SSL to connect securely (OpenSSH needs to be compiled with PHP)
- Encode emails in different formats
- Write your own connection handlers
- Write plugins with incredible control over the behaviour of the class
Swift.php
Code: Select all
<?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.16
* @author Chris Corbyn
* @date 9th September 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.16');
/**
* 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;
/**
* 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?
$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 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 (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/prop ... s-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);
}
$encoded = trim(chunk_split($this->encode($string, 'base64')));
$lines = explode("\r\n", $encoded);
return '=?'.$this->charset.'?B?'.implode("?=\r\n =?{$this->charset}?B?", $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;
}
}
?>Code: Select all
<?php
/**
* This is the SMTP handler for Swift Mailer, a PHP Mailer class.
*
* @package Swift
* @version >= 2.0.0
* @author Chris Corbyn
* @date 30th July 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 (at your option) 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_OPEN')) define('SWIFT_OPEN', 0);
if (!defined('SWIFT_SSL')) define('SWIFT_SSL', 1);
if (!defined('SWIFT_TLS')) define('SWIFT_TLS', 2);
if (!defined('SWIFT_DEFAULT_PORT')) define('SWIFT_DEFAULT_PORT', 25);
if (!defined('SWIFT_SECURE_PORT')) define('SWIFT_SECURE_PORT', 465);
if (!defined('SWIFT_AUTO_DETECT')) define('SWIFT_AUTO_DETECT', -2);
/**
* SMTP Connection Class.
* Connects to a remote MTA and stores the connections internally
* @package Swift
*/
class Swift_Connection_SMTP implements Swift_IConnection
{
/**
* Any errors we see
* @var string error
*/
public $error;
/**
* Just a boolean value for when we're connected
* @var bool connected
*/
public $connected = false;
/**
* SMTP Connection socket
* @var resource socket
*/
public $socket;
/**
* SMTP Read part of I/O for Swift
* @var resource socket (reference)
*/
public $readHook;
/**
* SMTP Write part of I/O for Swift
* @var resource socket (reference)
*/
public $writeHook;
/**
* Number of seconds to try to connect for (non-settable)
* @var int seconds timeout
*/
public $connectTimeout = 30;
/**
* SMTP server
* @var resource socket
*/
public $server;
/**
* SMTP Port (default 25)
* @var int port number
*/
public $port = SWIFT_DEFAULT_PORT;
/**
* Use SSL Encryption
* @var bool SSL
*/
public $ssl;
/**
* Constructor
* @param string SMTP server
* @param int SMTP Port, optional
* @param bool SSL
*/
public function __construct($server, $port=false, $transport=SWIFT_OPEN)
{
if ($server == SWIFT_AUTO_DETECT) $server = @ini_get('SMTP');
if ($port == SWIFT_AUTO_DETECT) $port = @ini_get('smtp_port');
$this->server = $server;
if ($port) $this->port = $port;
$this->ssl = $transport;
if ($transport && !$port)
{
$this->port = SWIFT_SECURE_PORT;
}
}
/**
* Establishes a connection with the MTA
* The SwiftInstance Object calls this
*
* @return bool connected
*/
public function start()
{
return $this->connect();
}
/**
* Establishes a connection with the MTA
*
* @return bool connected
* @private
*/
protected function connect()
{
$server = $this->server;
switch ($this->ssl)
{
case SWIFT_SSL: $protocol = 'ssl://';
break;
case SWIFT_TLS: $protocol = 'tls://';
break;
case SWIFT_OPEN:
default: $protocol = '';
}
$server = $protocol.$server;
$this->socket = @fsockopen($server, $this->port, $errno, $errstr, $this->connectTimeout);
$this->readHook =& $this->socket;
$this->writeHook =& $this->socket;
if (!$this->socket)
{
$this->error = $errstr;
return $this->connected = false;
}
else return $this->connected = true;
}
/**
* Closes the connection with the MTA
* Called by the SwiftInstance object
*
* @return void
*/
public function stop()
{
$this->disconnect();
}
/**
* Closes the connection with the MTA
* @return void
*/
protected function disconnect()
{
if ($this->connected && $this->socket)
{
fclose($this->socket);
$this->readHook = false;
$this->writeHook = false;
$this->socket = false;
$this->connected = false;
}
}
/**
* Returns TRUE if the socket is connected
* @return bool connected
*/
public function isConnected()
{
return $this->connected;
}
/**
* Change the default timeout from 30 seconds
* @param int timeout secs
* @return void
*/
public function setConnectTimeout($seconds)
{
$this->connectTimeout = (int) $seconds;
}
/**
* Destructor. Closes any open connections
*/
public function __destruct()
{
$this->disconnect();
}
}
?>Cheers,
d11