|
<?php
|
|
|
|
/**
|
|
* Swift Mailer Core Component.
|
|
* Please read the LICENSE file
|
|
* @copyright Chris Corbyn <chris@w3style.co.uk>
|
|
* @author Chris Corbyn <chris@w3style.co.uk>
|
|
* @package Swift
|
|
* @version 3.3.2
|
|
* @license GNU Lesser General Public License
|
|
*/
|
|
|
|
require_once dirname(__FILE__) . "/Swift/ClassLoader.php";
|
|
Swift_ClassLoader::load("Swift_LogContainer");
|
|
Swift_ClassLoader::load("Swift_ConnectionBase");
|
|
Swift_ClassLoader::load("Swift_BadResponseException");
|
|
Swift_ClassLoader::load("Swift_Cache");
|
|
Swift_ClassLoader::load("Swift_CacheFactory");
|
|
Swift_ClassLoader::load("Swift_Message");
|
|
Swift_ClassLoader::load("Swift_RecipientList");
|
|
Swift_ClassLoader::load("Swift_BatchMailer");
|
|
Swift_ClassLoader::load("Swift_Events");
|
|
Swift_ClassLoader::load("Swift_Events_Listener");
|
|
|
|
/**
|
|
* Swift is the central component in the Swift library.
|
|
* @package Swift
|
|
* @author Chris Corbyn <chris@w3style.co.uk>
|
|
* @version 3.3.2
|
|
*/
|
|
class Swift
|
|
{
|
|
/**
|
|
* The version number.
|
|
*/
|
|
const VERSION = "3.3.2";
|
|
/**
|
|
* Constant to flag Swift not to try and connect upon instantiation
|
|
*/
|
|
const NO_START = 2;
|
|
/**
|
|
* Constant to tell Swift not to perform the standard SMTP handshake upon connect
|
|
*/
|
|
const NO_HANDSHAKE = 4;
|
|
/**
|
|
* Constant to ask Swift to start logging
|
|
*/
|
|
const ENABLE_LOGGING = 8;
|
|
/**
|
|
* Constant to prevent postConnect() being run in the connection
|
|
*/
|
|
const NO_POST_CONNECT = 16;
|
|
/**
|
|
* The connection object currently active
|
|
* @var Swift_Connection
|
|
*/
|
|
public $connection = null;
|
|
/**
|
|
* The domain name of this server (should technically be a FQDN)
|
|
* @var string
|
|
*/
|
|
protected $domain = null;
|
|
/**
|
|
* Flags to change the behaviour of Swift
|
|
* @var int
|
|
*/
|
|
protected $options;
|
|
/**
|
|
* Loaded plugins, separated into containers according to roles
|
|
* @var array
|
|
*/
|
|
protected $listeners = array();
|
|
|
|
/**
|
|
* Constructor
|
|
* @param Swift_Connection The connection object to deal with I/O
|
|
* @param string The domain name of this server (the client) as a FQDN
|
|
* @param int Optional flags
|
|
* @throws Swift_ConnectionException If a connection cannot be established or the connection is behaving incorrectly
|
|
*/
|
|
public function __construct(Swift_Connection $conn, $domain=false, $options=null)
|
|
{
|
|
$this->initializeEventListenerContainer();
|
|
$this->setOptions($options);
|
|
|
|
$log = Swift_LogContainer::getLog();
|
|
|
|
if ($this->hasOption(self::ENABLE_LOGGING) && !$log->isEnabled())
|
|
{
|
|
$log->setLogLevel(Swift_Log::LOG_NETWORK);
|
|
}
|
|
|
|
if (!$domain) $domain = !empty($_SERVER["SERVER_ADDR"]) ? "[" . $_SERVER["SERVER_ADDR"] . "]" : "localhost.localdomain";
|
|
|
|
$this->setDomain($domain);
|
|
$this->connection = $conn;
|
|
|
|
if ($conn && !$this->hasOption(self::NO_START))
|
|
{
|
|
if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) $log->add("Trying to connect...", Swift_Log::NORMAL);
|
|
$this->connect();
|
|
}
|
|
}
|
|
/**
|
|
* Populate the listeners array with the defined listeners ready for plugins
|
|
*/
|
|
protected function initializeEventListenerContainer()
|
|
{
|
|
Swift_ClassLoader::load("Swift_Events_ListenerMapper");
|
|
foreach (Swift_Events_ListenerMapper::getMap() as $interface => $method)
|
|
{
|
|
if (!isset($this->listeners[$interface]))
|
|
$this->listeners[$interface] = array();
|
|
}
|
|
}
|
|
/**
|
|
* Add a new plugin to Swift
|
|
* Plugins must implement one or more event listeners
|
|
* @param Swift_Events_Listener The plugin to load
|
|
*/
|
|
public function attachPlugin(Swift_Events_Listener $plugin, $id)
|
|
{
|
|
foreach (array_keys($this->listeners) as $key)
|
|
{
|
|
$listener = "Swift_Events_" . $key;
|
|
Swift_ClassLoader::load($listener);
|
|
if ($plugin instanceof $listener) $this->listeners[$key][$id] = $plugin;
|
|
}
|
|
}
|
|
/**
|
|
* Get an attached plugin if it exists
|
|
* @param string The id of the plugin
|
|
* @return Swift_Event_Listener
|
|
*/
|
|
public function getPlugin($id)
|
|
{
|
|
foreach ($this->listeners as $type => $arr)
|
|
{
|
|
if (isset($arr[$id])) return $this->listeners[$type][$id];
|
|
}
|
|
return null; //If none found
|
|
}
|
|
/**
|
|
* Remove a plugin attached under the ID of $id
|
|
* @param string The ID of the plugin
|
|
*/
|
|
public function removePlugin($id)
|
|
{
|
|
foreach ($this->listeners as $type => $arr)
|
|
{
|
|
if (isset($arr[$id]))
|
|
{
|
|
$this->listeners[$type][$id] = null;
|
|
unset($this->listeners[$type][$id]);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Send a new type of event to all objects which are listening for it
|
|
* @param Swift_Events The event to send
|
|
* @param string The type of event
|
|
*/
|
|
public function notifyListeners($e, $type)
|
|
{
|
|
Swift_ClassLoader::load("Swift_Events_ListenerMapper");
|
|
if (!empty($this->listeners[$type]) && $notifyMethod = Swift_Events_ListenerMapper::getNotifyMethod($type))
|
|
{
|
|
$e->setSwift($this);
|
|
foreach ($this->listeners[$type] as $k => $listener)
|
|
{
|
|
$listener->$notifyMethod($e);
|
|
}
|
|
}
|
|
else $e = null;
|
|
}
|
|
/**
|
|
* Check if an option flag has been set
|
|
* @param string Option name
|
|
* @return boolean
|
|
*/
|
|
public function hasOption($option)
|
|
{
|
|
return ($this->options & $option);
|
|
}
|
|
/**
|
|
* Adjust the options flags
|
|
* E.g. $obj->setOptions(Swift::NO_START | Swift::NO_HANDSHAKE)
|
|
* @param int The bits to set
|
|
*/
|
|
public function setOptions($options)
|
|
{
|
|
$this->options = (int) $options;
|
|
}
|
|
/**
|
|
* Get the current options set (as bits)
|
|
* @return int
|
|
*/
|
|
public function getOptions()
|
|
{
|
|
return (int) $this->options;
|
|
}
|
|
/**
|
|
* Set the FQDN of this server as it will identify itself
|
|
* @param string The FQDN of the server
|
|
*/
|
|
public function setDomain($name)
|
|
{
|
|
$this->domain = (string) $name;
|
|
}
|
|
/**
|
|
* Attempt to establish a connection with the service
|
|
* @throws Swift_ConnectionException If the connection cannot be established or behaves oddly
|
|
*/
|
|
public function connect()
|
|
{
|
|
$this->connection->start();
|
|
$greeting = $this->command("", 220);
|
|
if (!$this->hasOption(self::NO_HANDSHAKE))
|
|
{
|
|
$this->handshake($greeting);
|
|
}
|
|
Swift_ClassLoader::load("Swift_Events_ConnectEvent");
|
|
$this->notifyListeners(new Swift_Events_ConnectEvent($this->connection), "ConnectListener");
|
|
}
|
|
/**
|
|
* Disconnect from the MTA
|
|
* @throws Swift_ConnectionException If the connection will not stop
|
|
*/
|
|
public function disconnect()
|
|
{
|
|
$this->command("QUIT");
|
|
$this->connection->stop();
|
|
Swift_ClassLoader::load("Swift_Events_DisconnectEvent");
|
|
$this->notifyListeners(new Swift_Events_DisconnectEvent($this->connection), "DisconnectListener");
|
|
}
|
|
/**
|
|
* Throws an exception if the response code wanted does not match the one returned
|
|
* @param Swift_Event_ResponseEvent The full response from the service
|
|
* @param int The 3 digit response code wanted
|
|
* @throws Swift_BadResponseException If the code does not match
|
|
*/
|
|
protected function assertCorrectResponse(Swift_Events_ResponseEvent $response, $codes)
|
|
{
|
|
$codes = (array)$codes;
|
|
if (!in_array($response->getCode(), $codes))
|
|
{
|
|
$log = Swift_LogContainer::getLog();
|
|
$error = "Expected response code(s) [" . implode(", ", $codes) . "] but got response [" . $response->getString() . "]";
|
|
if ($log->hasLevel(Swift_Log::LOG_ERRORS)) $log->add($error, Swift_Log::ERROR);
|
|
throw new Swift_BadResponseException($error);
|
|
}
|
|
}
|
|
/**
|
|
* Have a polite greeting with the server and work out what it's capable of
|
|
* @param Swift_Events_ResponseEvent The initial service line respoonse
|
|
* @throws Swift_ConnectionException If conversation is not going very well
|
|
*/
|
|
protected function handshake(Swift_Events_ResponseEvent $greeting)
|
|
{
|
|
if ($this->connection->getRequiresEHLO() || strpos($greeting->getString(), "ESMTP"))
|
|
$this->setConnectionExtensions($this->command("EHLO " . $this->domain, 250));
|
|
else $this->command("HELO " . $this->domain, 250);
|
|
//Connection might want to do something like authenticate now
|
|
if (!$this->hasOption(self::NO_POST_CONNECT)) $this->connection->postConnect($this);
|
|
}
|
|
/**
|
|
* Set the extensions which the service reports in the connection object
|
|
* @param Swift_Events_ResponseEvent The list of extensions as reported by the service
|
|
*/
|
|
protected function setConnectionExtensions(Swift_Events_ResponseEvent $list)
|
|
{
|
|
$le = (strpos($list->getString(), "\r\n") !== false) ? "\r\n" : "\n";
|
|
$list = explode($le, $list->getString());
|
|
for ($i = 1, $len = count($list); $i < $len; $i++)
|
|
{
|
|
$extension = substr($list[$i], 4);
|
|
$attributes = preg_split("/[ =]/", $extension);
|
|
$this->connection->setExtension($attributes[0], (isset($attributes[1]) ? array_slice($attributes, 1) : array()));
|
|
}
|
|
}
|
|
/**
|
|
* Execute a command against the service and get the response
|
|
* @param string The command to execute (leave off any CRLF!!!)
|
|
* @param int The code to check for in the response, if any. -1 indicates that no response is wanted.
|
|
* @return Swift_Events_ResponseEvent The server's response (could be multiple lines)
|
|
* @throws Swift_ConnectionException If a code was expected but does not match the one returned
|
|
*/
|
|
public function command($command, $code=null)
|
|
{
|
|
$log = Swift_LogContainer::getLog();
|
|
Swift_ClassLoader::load("Swift_Events_CommandEvent");
|
|
if ($command !== "")
|
|
{
|
|
$command_event = new Swift_Events_CommandEvent($command, $code);
|
|
$command = null; //For memory reasons
|
|
$this->notifyListeners($command_event, "BeforeCommandListener");
|
|
if ($log->hasLevel(Swift_Log::LOG_NETWORK) && $code != -1) $log->add($command_event->getString(), Swift_Log::COMMAND);
|
|
$end = ($code != -1) ? "\r\n" : null;
|
|
$this->connection->write($command_event->getString(), $end);
|
|
$this->notifyListeners($command_event, "CommandListener");
|
|
}
|
|
|
|
if ($code == -1) return null;
|
|
|
|
Swift_ClassLoader::load("Swift_Events_ResponseEvent");
|
|
$response_event = new Swift_Events_ResponseEvent($this->connection->read());
|
|
$this->notifyListeners($response_event, "ResponseListener");
|
|
if ($log->hasLevel(Swift_Log::LOG_NETWORK)) $log->add($response_event->getString(), Swift_Log::RESPONSE);
|
|
if ($command !== "" && $command_event->getCode() !== null)
|
|
$this->assertCorrectResponse($response_event, $command_event->getCode());
|
|
return $response_event;
|
|
}
|
|
/**
|
|
* Reset a conversation which has gone badly
|
|
* @throws Swift_ConnectionException If the service refuses to reset
|
|
*/
|
|
public function reset()
|
|
{
|
|
$this->command("RSET", 250);
|
|
}
|
|
/**
|
|
* Send a message to any number of recipients
|
|
* @param Swift_Message The message to send. This does not need to (and shouldn't really) have any of the recipient headers set.
|
|
* @param mixed The recipients to send to. Can be a string, Swift_Address or Swift_RecipientList. Note that all addresses apart from Bcc recipients will appear in the message headers
|
|
* @param mixed The address to send the message from. Can either be a string or an instance of Swift_Address.
|
|
* @return int The number of successful recipients
|
|
* @throws Swift_ConnectionException If sending fails for any reason.
|
|
*/
|
|
public function send(Swift_Message $message, $recipients, $from)
|
|
{
|
|
Swift_ClassLoader::load("Swift_Message_Encoder");
|
|
if (is_string($recipients) && preg_match("/^" . Swift_Message_Encoder::CHEAP_ADDRESS_RE . "\$/", $recipients))
|
|
{
|
|
$recipients = new Swift_Address($recipients);
|
|
}
|
|
elseif (!($recipients instanceof Swift_AddressContainer))
|
|
throw new Exception("The recipients parameter must either be a valid string email address, ".
|
|
"an instance of Swift_RecipientList or an instance of Swift_Address.");
|
|
|
|
if (is_string($from) && preg_match("/^" . Swift_Message_Encoder::CHEAP_ADDRESS_RE . "\$/", $from))
|
|
{
|
|
$from = new Swift_Address($from);
|
|
}
|
|
elseif (!($from instanceof Swift_Address))
|
|
throw new Exception("The sender parameter must either be a valid string email address or ".
|
|
"an instance of Swift_Address.");
|
|
|
|
$log = Swift_LogContainer::getLog();
|
|
|
|
if (!$message->getEncoding() && !$this->connection->hasExtension("8BITMIME"))
|
|
{
|
|
$message->setEncoding("QP", true, true);
|
|
}
|
|
|
|
$list = $recipients;
|
|
if ($recipients instanceof Swift_Address)
|
|
{
|
|
$list = new Swift_RecipientList();
|
|
$list->addTo($recipients);
|
|
}
|
|
|
|
Swift_ClassLoader::load("Swift_Events_SendEvent");
|
|
$send_event = new Swift_Events_SendEvent($message, $list, $from, 0);
|
|
|
|
$this->notifyListeners($send_event, "BeforeSendListener");
|
|
|
|
$to = $cc = array();
|
|
if (!($has_from = $message->getFrom())) $message->setFrom($from);
|
|
if (!($has_return_path = $message->getReturnPath())) $message->setReturnPath($from->build(true));
|
|
if (!($has_reply_to = $message->getReplyTo())) $message->setReplyTo($from);
|
|
if (!($has_message_id = $message->getId())) $message->generateId();
|
|
|
|
$this->command("MAIL FROM: " . $message->getReturnPath(true), 250);
|
|
|
|
$failed = 0;
|
|
$sent = 0;
|
|
$tmp_sent = 0;
|
|
|
|
$it = $list->getIterator("to");
|
|
while ($it->hasNext())
|
|
{
|
|
$it->next();
|
|
$address = $it->getValue();
|
|
$to[] = $address->build();
|
|
try {
|
|
$this->command("RCPT TO: " . $address->build(true), 250);
|
|
$tmp_sent++;
|
|
} catch (Swift_BadResponseException $e) {
|
|
$failed++;
|
|
$send_event->addFailedRecipient($address->getAddress());
|
|
if ($log->hasLevel(Swift_Log::LOG_FAILURES)) $log->addfailedRecipient($address->getAddress());
|
|
}
|
|
}
|
|
$it = $list->getIterator("cc");
|
|
while ($it->hasNext())
|
|
{
|
|
$it->next();
|
|
$address = $it->getValue();
|
|
$cc[] = $address->build();
|
|
try {
|
|
$this->command("RCPT TO: " . $address->build(true), 250);
|
|
$tmp_sent++;
|
|
} catch (Swift_BadResponseException $e) {
|
|
$failed++;
|
|
$send_event->addFailedRecipient($address->getAddress());
|
|
if ($log->hasLevel(Swift_Log::LOG_FAILURES)) $log->addfailedRecipient($address->getAddress());
|
|
}
|
|
}
|
|
|
|
if ($failed == (count($to) + count($cc)))
|
|
{
|
|
$this->reset();
|
|
$this->notifyListeners($send_event, "SendListener");
|
|
return 0;
|
|
}
|
|
|
|
if (!($has_to = $message->getTo()) && !empty($to)) $message->setTo($to);
|
|
if (!($has_cc = $message->getCc()) && !empty($cc)) $message->setCc($cc);
|
|
|
|
$this->command("DATA", 354);
|
|
$data = $message->build();
|
|
|
|
while (false !== $bytes = $data->read())
|
|
$this->command($bytes, -1);
|
|
if ($log->hasLevel(Swift_Log::LOG_NETWORK)) $log->add("<MESSAGE DATA>", Swift_Log::COMMAND);
|
|
try {
|
|
$this->command("\r\n.", 250);
|
|
$sent += $tmp_sent;
|
|
} catch (Swift_BadResponseException $e) {
|
|
$failed += $tmp_sent;
|
|
}
|
|
|
|
$tmp_sent = 0;
|
|
$has_bcc = $message->getBcc();
|
|
$it = $list->getIterator("bcc");
|
|
while ($it->hasNext())
|
|
{
|
|
$it->next();
|
|
$address = $it->getValue();
|
|
if (!$has_bcc) $message->setBcc($address->build());
|
|
try {
|
|
$this->command("MAIL FROM: " . $message->getReturnPath(true), 250);
|
|
$this->command("RCPT TO: " . $address->build(true), 250);
|
|
$this->command("DATA", 354);
|
|
$data = $message->build();
|
|
while (false !== $bytes = $data->read())
|
|
$this->command($bytes, -1);
|
|
if ($log->hasLevel(Swift_Log::LOG_NETWORK)) $log->add("<MESSAGE DATA>", Swift_Log::COMMAND);
|
|
$this->command("\r\n.", 250);
|
|
$sent++;
|
|
} catch (Swift_BadResponseException $e) {
|
|
$failed++;
|
|
$send_event->addFailedRecipient($address->getAddress());
|
|
if ($log->hasLevel(Swift_Log::LOG_FAILURES)) $log->addfailedRecipient($address->getAddress());
|
|
$this->reset();
|
|
}
|
|
}
|
|
|
|
$total = count($to) + count($cc) + count($list->getBcc());
|
|
|
|
$send_event->setNumSent($sent);
|
|
$this->notifyListeners($send_event, "SendListener");
|
|
|
|
if (!$has_return_path) $message->setReturnPath("");
|
|
if (!$has_from) $message->setFrom("");
|
|
if (!$has_to) $message->setTo("");
|
|
if (!$has_reply_to) $message->setReplyTo(null);
|
|
if (!$has_cc) $message->setCc(null);
|
|
if (!$has_bcc) $message->setBcc(null);
|
|
if (!$has_message_id) $message->setId(null);
|
|
|
|
if ($log->hasLevel(Swift_Log::LOG_NETWORK)) $log->add("Message sent to " . $sent . "/" . $total . " recipients", Swift_Log::NORMAL);
|
|
|
|
return $sent;
|
|
}
|
|
/**
|
|
* Send a message to a batch of recipients.
|
|
* Unlike send() this method ignores Cc and Bcc recipients and does not reveal every recipients' address in the headers
|
|
* @param Swift_Message The message to send (leave out the recipient headers unless you are deliberately overriding them)
|
|
* @param Swift_RecipientList The addresses to send to
|
|
* @param Swift_Address The address the mail is from (sender)
|
|
* @return int The number of successful recipients
|
|
*/
|
|
public function batchSend(Swift_Message $message, Swift_RecipientList $to, $from)
|
|
{
|
|
$batch = new Swift_BatchMailer($this);
|
|
return $batch->send($message, $to, $from);
|
|
}
|
|
}
|