TYPO3 API  SVNRelease
AbstractSmtpTransport.php
Go to the documentation of this file.
00001 <?php
00002 
00003 /*
00004  * This file is part of SwiftMailer.
00005  * (c) 2004-2009 Chris Corbyn
00006  *
00007  * For the full copyright and license information, please view the LICENSE
00008  * file that was distributed with this source code.
00009  */
00010 
00011 //@require 'Swift/Transport.php';
00012 //@require 'Swift/Transport/IoBuffer.php';
00013 //@require 'Swift/Transport/CommandSentException.php';
00014 //@require 'Swift/TransportException.php';
00015 //@require 'Swift/Mime/Message.php';
00016 //@require 'Swift/Events/EventDispatcher.php';
00017 //@require 'Swift/Events/EventListener.php';
00018 
00019 /**
00020  * Sends Messages over SMTP.
00021  *
00022  * @package Swift
00023  * @subpackage Transport
00024  * @author Chris Corbyn
00025  */
00026 abstract class Swift_Transport_AbstractSmtpTransport
00027   implements Swift_Transport
00028 {
00029 
00030   /** Input-Output buffer for sending/receiving SMTP commands and responses */
00031   protected $_buffer;
00032 
00033   /** Connection status */
00034   protected $_started = false;
00035 
00036   /** The domain name to use in HELO command */
00037   protected $_domain = '[127.0.0.1]';
00038 
00039   /** The event dispatching layer */
00040   protected $_eventDispatcher;
00041 
00042   /** Return an array of params for the Buffer */
00043   abstract protected function _getBufferParams();
00044 
00045   /**
00046    * Creates a new EsmtpTransport using the given I/O buffer.
00047    *
00048    * @param Swift_Transport_IoBuffer $buf
00049    * @param Swift_Events_EventDispatcher $dispatcher
00050    */
00051   public function __construct(Swift_Transport_IoBuffer $buf,
00052     Swift_Events_EventDispatcher $dispatcher)
00053   {
00054     $this->_eventDispatcher = $dispatcher;
00055     $this->_buffer = $buf;
00056     $this->_lookupHostname();
00057   }
00058 
00059   /**
00060    * Set the name of the local domain which Swift will identify itself as.
00061    * This should be a fully-qualified domain name and should be truly the domain
00062    * you're using.  If your server doesn't have a domain name, use the IP in square
00063    * brackets (i.e. [127.0.0.1]).
00064    *
00065    * @param string $domain
00066    */
00067   public function setLocalDomain($domain)
00068   {
00069     $this->_domain = $domain;
00070     return $this;
00071   }
00072 
00073   /**
00074    * Get the name of the domain Swift will identify as.
00075    *
00076    * @return string
00077    */
00078   public function getLocalDomain()
00079   {
00080     return $this->_domain;
00081   }
00082 
00083   /**
00084    * Start the SMTP connection.
00085    */
00086   public function start()
00087   {
00088     if (!$this->_started)
00089     {
00090       if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this))
00091       {
00092         $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStarted');
00093         if ($evt->bubbleCancelled())
00094         {
00095           return;
00096         }
00097       }
00098 
00099       try
00100       {
00101         $this->_buffer->initialize($this->_getBufferParams());
00102       }
00103       catch (Swift_TransportException $e)
00104       {
00105         $this->_throwException($e);
00106       }
00107       $this->_readGreeting();
00108       $this->_doHeloCommand();
00109 
00110       if ($evt)
00111       {
00112         $this->_eventDispatcher->dispatchEvent($evt, 'transportStarted');
00113       }
00114 
00115       $this->_started = true;
00116     }
00117   }
00118 
00119   /**
00120    * Test if an SMTP connection has been established.
00121    *
00122    * @return boolean
00123    */
00124   public function isStarted()
00125   {
00126     return $this->_started;
00127   }
00128 
00129   /**
00130    * Send the given Message.
00131    *
00132    * Recipient/sender data will be retreived from the Message API.
00133    * The return value is the number of recipients who were accepted for delivery.
00134    *
00135    * @param Swift_Mime_Message $message
00136    * @param string[] &$failedRecipients to collect failures by-reference
00137    * @return int
00138    */
00139   public function send(Swift_Mime_Message $message, &$failedRecipients = null)
00140   {
00141     $sent = 0;
00142     $failedRecipients = (array) $failedRecipients;
00143 
00144     if (!$reversePath = $this->_getReversePath($message))
00145     {
00146       throw new Swift_TransportException(
00147         'Cannot send message without a sender address'
00148         );
00149     }
00150 
00151     if ($evt = $this->_eventDispatcher->createSendEvent($this, $message))
00152     {
00153       $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
00154       if ($evt->bubbleCancelled())
00155       {
00156         return 0;
00157       }
00158     }
00159 
00160     $to = (array) $message->getTo();
00161     $cc = (array) $message->getCc();
00162     $bcc = (array) $message->getBcc();
00163 
00164     $message->setBcc(array());
00165 
00166     try
00167     {
00168       $sent += $this->_sendTo($message, $reversePath, $to, $failedRecipients);
00169       $sent += $this->_sendCc($message, $reversePath, $cc, $failedRecipients);
00170       $sent += $this->_sendBcc($message, $reversePath, $bcc, $failedRecipients);
00171     }
00172     catch (Exception $e)
00173     {
00174       $message->setBcc($bcc);
00175       throw $e;
00176     }
00177 
00178     $message->setBcc($bcc);
00179 
00180     if ($evt)
00181     {
00182       if ($sent == count($to) + count($cc) + count($bcc))
00183       {
00184         $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
00185       }
00186       elseif ($sent > 0)
00187       {
00188         $evt->setResult(Swift_Events_SendEvent::RESULT_TENTATIVE);
00189       }
00190       else
00191       {
00192         $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED);
00193       }
00194       $evt->setFailedRecipients($failedRecipients);
00195       $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
00196     }
00197 
00198     $message->generateId(); //Make sure a new Message ID is used
00199 
00200     return $sent;
00201   }
00202 
00203   /**
00204    * Stop the SMTP connection.
00205    */
00206   public function stop()
00207   {
00208     if ($this->_started)
00209     {
00210       if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this))
00211       {
00212         $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStopped');
00213         if ($evt->bubbleCancelled())
00214         {
00215           return;
00216         }
00217       }
00218 
00219       try
00220       {
00221         $this->executeCommand("QUIT\r\n", array(221));
00222       }
00223       catch (Swift_TransportException $e) {}
00224 
00225       try
00226       {
00227         $this->_buffer->terminate();
00228 
00229         if ($evt)
00230         {
00231           $this->_eventDispatcher->dispatchEvent($evt, 'transportStopped');
00232         }
00233       }
00234       catch (Swift_TransportException $e)
00235       {
00236         $this->_throwException($e);
00237       }
00238     }
00239     $this->_started = false;
00240   }
00241 
00242   /**
00243    * Register a plugin.
00244    *
00245    * @param Swift_Events_EventListener $plugin
00246    */
00247   public function registerPlugin(Swift_Events_EventListener $plugin)
00248   {
00249     $this->_eventDispatcher->bindEventListener($plugin);
00250   }
00251 
00252   /**
00253    * Reset the current mail transaction.
00254    */
00255   public function reset()
00256   {
00257     $this->executeCommand("RSET\r\n", array(250));
00258   }
00259 
00260   /**
00261    * Get the IoBuffer where read/writes are occurring.
00262    *
00263    * @return Swift_Transport_IoBuffer
00264    */
00265   public function getBuffer()
00266   {
00267     return $this->_buffer;
00268   }
00269 
00270   /**
00271    * Run a command against the buffer, expecting the given response codes.
00272    *
00273    * If no response codes are given, the response will not be validated.
00274    * If codes are given, an exception will be thrown on an invalid response.
00275    *
00276    * @param string $command
00277    * @param int[] $codes
00278    * @param string[] &$failures
00279    * @return string
00280    */
00281   public function executeCommand($command, $codes = array(), &$failures = null)
00282   {
00283     $failures = (array) $failures;
00284     $seq = $this->_buffer->write($command);
00285     $response = $this->_getFullResponse($seq);
00286     if ($evt = $this->_eventDispatcher->createCommandEvent($this, $command, $codes))
00287     {
00288       $this->_eventDispatcher->dispatchEvent($evt, 'commandSent');
00289     }
00290     $this->_assertResponseCode($response, $codes);
00291     return $response;
00292   }
00293 
00294   // -- Protected methods
00295 
00296   /** Read the opening SMTP greeting */
00297   protected function _readGreeting()
00298   {
00299     $this->_assertResponseCode($this->_getFullResponse(0), array(220));
00300   }
00301 
00302   /** Send the HELO welcome */
00303   protected function _doHeloCommand()
00304   {
00305     $this->executeCommand(
00306       sprintf("HELO %s\r\n", $this->_domain), array(250)
00307       );
00308   }
00309 
00310   /** Send the MAIL FROM command */
00311   protected function _doMailFromCommand($address)
00312   {
00313     $this->executeCommand(
00314       sprintf("MAIL FROM: <%s>\r\n", $address), array(250)
00315       );
00316   }
00317 
00318   /** Send the RCPT TO command */
00319   protected function _doRcptToCommand($address)
00320   {
00321     $this->executeCommand(
00322       sprintf("RCPT TO: <%s>\r\n", $address), array(250, 251, 252)
00323       );
00324   }
00325 
00326   /** Send the DATA command */
00327   protected function _doDataCommand()
00328   {
00329     $this->executeCommand("DATA\r\n", array(354));
00330   }
00331 
00332   /** Stream the contents of the message over the buffer */
00333   protected function _streamMessage(Swift_Mime_Message $message)
00334   {
00335     $this->_buffer->setWriteTranslations(array("\r\n." => "\r\n.."));
00336     try
00337     {
00338       $message->toByteStream($this->_buffer);
00339       $this->_buffer->flushBuffers();
00340     }
00341     catch (Swift_TransportException $e)
00342     {
00343       $this->_throwException($e);
00344     }
00345     $this->_buffer->setWriteTranslations(array());
00346     $this->executeCommand("\r\n.\r\n", array(250));
00347   }
00348 
00349   /** Determine the best-use reverse path for this message */
00350   protected function _getReversePath(Swift_Mime_Message $message)
00351   {
00352     $return = $message->getReturnPath();
00353     $sender = $message->getSender();
00354     $from = $message->getFrom();
00355     $path = null;
00356     if (!empty($return))
00357     {
00358       $path = $return;
00359     }
00360     elseif (!empty($sender))
00361     {
00362       // Don't use array_keys
00363       reset($sender); // Reset Pointer to first pos
00364       $path = key($sender); // Get key
00365     }
00366     elseif (!empty($from))
00367     {
00368       reset($from); // Reset Pointer to first pos
00369       $path = key($from); // Get key
00370     }
00371     return $path;
00372   }
00373 
00374   /** Throw a TransportException, first sending it to any listeners */
00375   protected function _throwException(Swift_TransportException $e)
00376   {
00377     if ($evt = $this->_eventDispatcher->createTransportExceptionEvent($this, $e))
00378     {
00379       $this->_eventDispatcher->dispatchEvent($evt, 'exceptionThrown');
00380       if (!$evt->bubbleCancelled())
00381       {
00382         throw $e;
00383       }
00384     }
00385     else
00386     {
00387       throw $e;
00388     }
00389   }
00390 
00391   /** Throws an Exception if a response code is incorrect */
00392   protected function _assertResponseCode($response, $wanted)
00393   {
00394     list($code, $separator, $text) = sscanf($response, '%3d%[ -]%s');
00395     $valid = (empty($wanted) || in_array($code, $wanted));
00396 
00397     if ($evt = $this->_eventDispatcher->createResponseEvent($this, $response,
00398       $valid))
00399     {
00400       $this->_eventDispatcher->dispatchEvent($evt, 'responseReceived');
00401     }
00402 
00403     if (!$valid)
00404     {
00405       $this->_throwException(
00406         new Swift_TransportException(
00407           'Expected response code ' . implode('/', $wanted) . ' but got code ' .
00408           '"' . $code . '", with message "' . $response . '"'
00409           )
00410         );
00411     }
00412   }
00413 
00414   /** Get an entire multi-line response using its sequence number */
00415   protected function _getFullResponse($seq)
00416   {
00417     $response = '';
00418     try
00419     {
00420       do
00421       {
00422         $line = $this->_buffer->readLine($seq);
00423         $response .= $line;
00424       }
00425       while (null !== $line && false !== $line && ' ' != $line{3});
00426     }
00427     catch (Swift_TransportException $e)
00428     {
00429       $this->_throwException($e);
00430     }
00431     return $response;
00432   }
00433 
00434   // -- Private methods
00435 
00436   /** Send an email to the given recipients from the given reverse path */
00437   private function _doMailTransaction($message, $reversePath,
00438     array $recipients, array &$failedRecipients)
00439   {
00440     $sent = 0;
00441     $this->_doMailFromCommand($reversePath);
00442     foreach ($recipients as $forwardPath)
00443     {
00444       try
00445       {
00446         $this->_doRcptToCommand($forwardPath);
00447         $sent++;
00448       }
00449       catch (Swift_TransportException $e)
00450       {
00451         $failedRecipients[] = $forwardPath;
00452       }
00453     }
00454 
00455     if ($sent != 0)
00456     {
00457       $this->_doDataCommand();
00458       $this->_streamMessage($message);
00459     }
00460     else
00461     {
00462       $this->reset();
00463     }
00464 
00465     return $sent;
00466   }
00467 
00468   /** Send a message to the given To: recipients */
00469   private function _sendTo(Swift_Mime_Message $message, $reversePath,
00470     array $to, array &$failedRecipients)
00471   {
00472     if (empty($to))
00473     {
00474       return 0;
00475     }
00476     return $this->_doMailTransaction($message, $reversePath, array_keys($to),
00477       $failedRecipients);
00478   }
00479 
00480   /** Send a message to the given Cc: recipients */
00481   private function _sendCc(Swift_Mime_Message $message, $reversePath,
00482     array $cc, array &$failedRecipients)
00483   {
00484     if (empty($cc))
00485     {
00486       return 0;
00487     }
00488     return $this->_doMailTransaction($message, $reversePath, array_keys($cc),
00489       $failedRecipients);
00490   }
00491 
00492   /** Send a message to all Bcc: recipients */
00493   private function _sendBcc(Swift_Mime_Message $message, $reversePath,
00494     array $bcc, array &$failedRecipients)
00495   {
00496     $sent = 0;
00497     foreach ($bcc as $forwardPath => $name)
00498     {
00499       $message->setBcc(array($forwardPath => $name));
00500       $sent += $this->_doMailTransaction(
00501         $message, $reversePath, array($forwardPath), $failedRecipients
00502         );
00503     }
00504     return $sent;
00505   }
00506 
00507   /** Try to determine the hostname of the server this is run on */
00508   private function _lookupHostname()
00509   {
00510     if (!empty($_SERVER['SERVER_NAME'])
00511       && $this->_isFqdn($_SERVER['SERVER_NAME']))
00512     {
00513       $this->_domain = $_SERVER['SERVER_NAME'];
00514     }
00515     elseif (!empty($_SERVER['SERVER_ADDR']))
00516     {
00517       $this->_domain = sprintf('[%s]', $_SERVER['SERVER_ADDR']);
00518     }
00519   }
00520 
00521   /** Determine is the $hostname is a fully-qualified name */
00522   private function _isFqdn($hostname)
00523   {
00524     //We could do a really thorough check, but there's really no point
00525     if (false !== $dotPos = strpos($hostname, '.'))
00526     {
00527       return ($dotPos > 0) && ($dotPos != strlen($hostname) - 1);
00528     }
00529     else
00530     {
00531       return false;
00532     }
00533   }
00534 
00535   /**
00536    * Destructor.
00537    */
00538   public function __destruct()
00539   {
00540     $this->stop();
00541   }
00542 
00543 }