4
* This file is part of SwiftMailer.
5
* (c) 2004-2009 Chris Corbyn
7
* For the full copyright and license information, please view the LICENSE
8
* file that was distributed with this source code.
11
//@require 'Swift/Transport.php';
12
//@require 'Swift/Transport/IoBuffer.php';
13
//@require 'Swift/Transport/CommandSentException.php';
14
//@require 'Swift/TransportException.php';
15
//@require 'Swift/Mime/Message.php';
16
//@require 'Swift/Events/EventDispatcher.php';
17
//@require 'Swift/Events/EventListener.php';
20
* Sends Messages over SMTP.
23
* @subpackage Transport
24
* @author Chris Corbyn
26
abstract class Swift_Transport_AbstractSmtpTransport
27
implements Swift_Transport
30
/** Input-Output buffer for sending/receiving SMTP commands and responses */
33
/** Connection status */
34
protected $_started = false;
36
/** The domain name to use in HELO command */
37
protected $_domain = '[127.0.0.1]';
39
/** The event dispatching layer */
40
protected $_eventDispatcher;
42
/** Return an array of params for the Buffer */
43
abstract protected function _getBufferParams();
46
* Creates a new EsmtpTransport using the given I/O buffer.
48
* @param Swift_Transport_IoBuffer $buf
49
* @param Swift_Events_EventDispatcher $dispatcher
51
public function __construct(Swift_Transport_IoBuffer $buf,
52
Swift_Events_EventDispatcher $dispatcher)
54
$this->_eventDispatcher = $dispatcher;
55
$this->_buffer = $buf;
56
$this->_lookupHostname();
60
* Set the name of the local domain which Swift will identify itself as.
61
* This should be a fully-qualified domain name and should be truly the domain
62
* you're using. If your server doesn't have a domain name, use the IP in square
63
* brackets (i.e. [127.0.0.1]).
65
* @param string $domain
67
public function setLocalDomain($domain)
69
$this->_domain = $domain;
74
* Get the name of the domain Swift will identify as.
78
public function getLocalDomain()
80
return $this->_domain;
84
* Start the SMTP connection.
86
public function start()
90
if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this))
92
$this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStarted');
93
if ($evt->bubbleCancelled())
101
$this->_buffer->initialize($this->_getBufferParams());
103
catch (Swift_TransportException $e)
105
$this->_throwException($e);
107
$this->_readGreeting();
108
$this->_doHeloCommand();
112
$this->_eventDispatcher->dispatchEvent($evt, 'transportStarted');
115
$this->_started = true;
120
* Test if an SMTP connection has been established.
124
public function isStarted()
126
return $this->_started;
130
* Send the given Message.
132
* Recipient/sender data will be retreived from the Message API.
133
* The return value is the number of recipients who were accepted for delivery.
135
* @param Swift_Mime_Message $message
136
* @param string[] &$failedRecipients to collect failures by-reference
139
public function send(Swift_Mime_Message $message, &$failedRecipients = null)
142
$failedRecipients = (array) $failedRecipients;
144
if (!$reversePath = $this->_getReversePath($message))
146
throw new Swift_TransportException(
147
'Cannot send message without a sender address'
151
if ($evt = $this->_eventDispatcher->createSendEvent($this, $message))
153
$this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
154
if ($evt->bubbleCancelled())
160
$to = (array) $message->getTo();
161
$cc = (array) $message->getCc();
162
$bcc = (array) $message->getBcc();
164
$message->setBcc(array());
168
$sent += $this->_sendTo($message, $reversePath, $to, $failedRecipients);
169
$sent += $this->_sendCc($message, $reversePath, $cc, $failedRecipients);
170
$sent += $this->_sendBcc($message, $reversePath, $bcc, $failedRecipients);
174
$message->setBcc($bcc);
178
$message->setBcc($bcc);
182
if ($sent == count($to) + count($cc) + count($bcc))
184
$evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
188
$evt->setResult(Swift_Events_SendEvent::RESULT_TENTATIVE);
192
$evt->setResult(Swift_Events_SendEvent::RESULT_FAILED);
194
$evt->setFailedRecipients($failedRecipients);
195
$this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
198
$message->generateId(); //Make sure a new Message ID is used
204
* Stop the SMTP connection.
206
public function stop()
210
if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this))
212
$this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStopped');
213
if ($evt->bubbleCancelled())
221
$this->executeCommand("QUIT\r\n", array(221));
223
catch (Swift_TransportException $e) {}
227
$this->_buffer->terminate();
231
$this->_eventDispatcher->dispatchEvent($evt, 'transportStopped');
234
catch (Swift_TransportException $e)
236
$this->_throwException($e);
239
$this->_started = false;
245
* @param Swift_Events_EventListener $plugin
247
public function registerPlugin(Swift_Events_EventListener $plugin)
249
$this->_eventDispatcher->bindEventListener($plugin);
253
* Reset the current mail transaction.
255
public function reset()
257
$this->executeCommand("RSET\r\n", array(250));
261
* Get the IoBuffer where read/writes are occurring.
263
* @return Swift_Transport_IoBuffer
265
public function getBuffer()
267
return $this->_buffer;
271
* Run a command against the buffer, expecting the given response codes.
273
* If no response codes are given, the response will not be validated.
274
* If codes are given, an exception will be thrown on an invalid response.
276
* @param string $command
277
* @param int[] $codes
278
* @param string[] &$failures
281
public function executeCommand($command, $codes = array(), &$failures = null)
283
$failures = (array) $failures;
284
$seq = $this->_buffer->write($command);
285
$response = $this->_getFullResponse($seq);
286
if ($evt = $this->_eventDispatcher->createCommandEvent($this, $command, $codes))
288
$this->_eventDispatcher->dispatchEvent($evt, 'commandSent');
290
$this->_assertResponseCode($response, $codes);
294
// -- Protected methods
296
/** Read the opening SMTP greeting */
297
protected function _readGreeting()
299
$this->_assertResponseCode($this->_getFullResponse(0), array(220));
302
/** Send the HELO welcome */
303
protected function _doHeloCommand()
305
$this->executeCommand(
306
sprintf("HELO %s\r\n", $this->_domain), array(250)
310
/** Send the MAIL FROM command */
311
protected function _doMailFromCommand($address)
313
$this->executeCommand(
314
sprintf("MAIL FROM: <%s>\r\n", $address), array(250)
318
/** Send the RCPT TO command */
319
protected function _doRcptToCommand($address)
321
$this->executeCommand(
322
sprintf("RCPT TO: <%s>\r\n", $address), array(250, 251, 252)
326
/** Send the DATA command */
327
protected function _doDataCommand()
329
$this->executeCommand("DATA\r\n", array(354));
332
/** Stream the contents of the message over the buffer */
333
protected function _streamMessage(Swift_Mime_Message $message)
335
$this->_buffer->setWriteTranslations(array("\r\n." => "\r\n.."));
338
$message->toByteStream($this->_buffer);
339
$this->_buffer->flushBuffers();
341
catch (Swift_TransportException $e)
343
$this->_throwException($e);
345
$this->_buffer->setWriteTranslations(array());
346
$this->executeCommand("\r\n.\r\n", array(250));
349
/** Determine the best-use reverse path for this message */
350
protected function _getReversePath(Swift_Mime_Message $message)
352
$return = $message->getReturnPath();
353
$sender = $message->getSender();
354
$from = $message->getFrom();
360
elseif (!empty($sender))
362
// Don't use array_keys
363
reset($sender); // Reset Pointer to first pos
364
$path = key($sender); // Get key
366
elseif (!empty($from))
368
reset($from); // Reset Pointer to first pos
369
$path = key($from); // Get key
374
/** Throw a TransportException, first sending it to any listeners */
375
protected function _throwException(Swift_TransportException $e)
377
if ($evt = $this->_eventDispatcher->createTransportExceptionEvent($this, $e))
379
$this->_eventDispatcher->dispatchEvent($evt, 'exceptionThrown');
380
if (!$evt->bubbleCancelled())
391
/** Throws an Exception if a response code is incorrect */
392
protected function _assertResponseCode($response, $wanted)
394
list($code, $separator, $text) = sscanf($response, '%3d%[ -]%s');
395
$valid = (empty($wanted) || in_array($code, $wanted));
397
if ($evt = $this->_eventDispatcher->createResponseEvent($this, $response,
400
$this->_eventDispatcher->dispatchEvent($evt, 'responseReceived');
405
$this->_throwException(
406
new Swift_TransportException(
407
'Expected response code ' . implode('/', $wanted) . ' but got code ' .
408
'"' . $code . '", with message "' . $response . '"'
414
/** Get an entire multi-line response using its sequence number */
415
protected function _getFullResponse($seq)
422
$line = $this->_buffer->readLine($seq);
425
while (null !== $line && false !== $line && ' ' != $line{3});
427
catch (Swift_TransportException $e)
429
$this->_throwException($e);
434
// -- Private methods
436
/** Send an email to the given recipients from the given reverse path */
437
private function _doMailTransaction($message, $reversePath,
438
array $recipients, array &$failedRecipients)
441
$this->_doMailFromCommand($reversePath);
442
foreach ($recipients as $forwardPath)
446
$this->_doRcptToCommand($forwardPath);
449
catch (Swift_TransportException $e)
451
$failedRecipients[] = $forwardPath;
457
$this->_doDataCommand();
458
$this->_streamMessage($message);
468
/** Send a message to the given To: recipients */
469
private function _sendTo(Swift_Mime_Message $message, $reversePath,
470
array $to, array &$failedRecipients)
476
return $this->_doMailTransaction($message, $reversePath, array_keys($to),
480
/** Send a message to the given Cc: recipients */
481
private function _sendCc(Swift_Mime_Message $message, $reversePath,
482
array $cc, array &$failedRecipients)
488
return $this->_doMailTransaction($message, $reversePath, array_keys($cc),
492
/** Send a message to all Bcc: recipients */
493
private function _sendBcc(Swift_Mime_Message $message, $reversePath,
494
array $bcc, array &$failedRecipients)
497
foreach ($bcc as $forwardPath => $name)
499
$message->setBcc(array($forwardPath => $name));
500
$sent += $this->_doMailTransaction(
501
$message, $reversePath, array($forwardPath), $failedRecipients
507
/** Try to determine the hostname of the server this is run on */
508
private function _lookupHostname()
510
if (!empty($_SERVER['SERVER_NAME'])
511
&& $this->_isFqdn($_SERVER['SERVER_NAME']))
513
$this->_domain = $_SERVER['SERVER_NAME'];
515
elseif (!empty($_SERVER['SERVER_ADDR']))
517
$this->_domain = sprintf('[%s]', $_SERVER['SERVER_ADDR']);
521
/** Determine is the $hostname is a fully-qualified name */
522
private function _isFqdn($hostname)
524
//We could do a really thorough check, but there's really no point
525
if (false !== $dotPos = strpos($hostname, '.'))
527
return ($dotPos > 0) && ($dotPos != strlen($hostname) - 1);
538
public function __destruct()