PageRenderTime 82ms CodeModel.GetById 36ms app.highlight 38ms RepoModel.GetById 2ms app.codeStats 0ms

/framework/vendor/swift/lib/classes/Swift/Transport/AbstractSmtpTransport.php

http://zoop.googlecode.com/
PHP | 543 lines | 372 code | 58 blank | 113 comment | 34 complexity | 5a2fa311d1790f92d23c64082fd1523a MD5 | raw file
  1<?php
  2
  3/*
  4 * This file is part of SwiftMailer.
  5 * (c) 2004-2009 Chris Corbyn
  6 *
  7 * For the full copyright and license information, please view the LICENSE
  8 * file that was distributed with this source code.
  9 */
 10
 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';
 18
 19/**
 20 * Sends Messages over SMTP.
 21 * 
 22 * @package Swift
 23 * @subpackage Transport
 24 * @author Chris Corbyn
 25 */
 26abstract class Swift_Transport_AbstractSmtpTransport
 27  implements Swift_Transport
 28{
 29  
 30  /** Input-Output buffer for sending/receiving SMTP commands and responses */
 31  protected $_buffer;
 32  
 33  /** Connection status */
 34  protected $_started = false;
 35  
 36  /** The domain name to use in HELO command */
 37  protected $_domain = '[127.0.0.1]';
 38  
 39  /** The event dispatching layer */
 40  protected $_eventDispatcher;
 41  
 42  /** Return an array of params for the Buffer */
 43  abstract protected function _getBufferParams();
 44  
 45  /**
 46   * Creates a new EsmtpTransport using the given I/O buffer.
 47   * 
 48   * @param Swift_Transport_IoBuffer $buf
 49   * @param Swift_Events_EventDispatcher $dispatcher
 50   */
 51  public function __construct(Swift_Transport_IoBuffer $buf,
 52    Swift_Events_EventDispatcher $dispatcher)
 53  {
 54    $this->_eventDispatcher = $dispatcher;
 55    $this->_buffer = $buf;
 56    $this->_lookupHostname();
 57  }
 58  
 59  /**
 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]).
 64   * 
 65   * @param string $domain
 66   */
 67  public function setLocalDomain($domain)
 68  {
 69    $this->_domain = $domain;
 70    return $this;
 71  }
 72  
 73  /**
 74   * Get the name of the domain Swift will identify as.
 75   * 
 76   * @return string
 77   */
 78  public function getLocalDomain()
 79  {
 80    return $this->_domain;
 81  }
 82  
 83  /**
 84   * Start the SMTP connection.
 85   */
 86  public function start()
 87  {
 88    if (!$this->_started)
 89    {
 90      if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this))
 91      {
 92        $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStarted');
 93        if ($evt->bubbleCancelled())
 94        {
 95          return;
 96        }
 97      }
 98      
 99      try
100      {
101        $this->_buffer->initialize($this->_getBufferParams());
102      }
103      catch (Swift_TransportException $e)
104      {
105        $this->_throwException($e);
106      }
107      $this->_readGreeting();
108      $this->_doHeloCommand();
109      
110      if ($evt)
111      {
112        $this->_eventDispatcher->dispatchEvent($evt, 'transportStarted');
113      }
114      
115      $this->_started = true;
116    }
117  }
118  
119  /**
120   * Test if an SMTP connection has been established.
121   * 
122   * @return boolean
123   */
124  public function isStarted()
125  {
126    return $this->_started;
127  }
128  
129  /**
130   * Send the given Message.
131   * 
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.
134   * 
135   * @param Swift_Mime_Message $message
136   * @param string[] &$failedRecipients to collect failures by-reference
137   * @return int
138   */
139  public function send(Swift_Mime_Message $message, &$failedRecipients = null)
140  {
141    $sent = 0;
142    $failedRecipients = (array) $failedRecipients;
143    
144    if (!$reversePath = $this->_getReversePath($message))
145    {
146      throw new Swift_TransportException(
147        'Cannot send message without a sender address'
148        );
149    }
150    
151    if ($evt = $this->_eventDispatcher->createSendEvent($this, $message))
152    {
153      $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
154      if ($evt->bubbleCancelled())
155      {
156        return 0;
157      }
158    }
159    
160    $to = (array) $message->getTo();
161    $cc = (array) $message->getCc();
162    $bcc = (array) $message->getBcc();
163    
164    $message->setBcc(array());
165    
166    try
167    {
168      $sent += $this->_sendTo($message, $reversePath, $to, $failedRecipients);
169      $sent += $this->_sendCc($message, $reversePath, $cc, $failedRecipients);
170      $sent += $this->_sendBcc($message, $reversePath, $bcc, $failedRecipients);
171    }
172    catch (Exception $e)
173    {
174      $message->setBcc($bcc);
175      throw $e;
176    }
177    
178    $message->setBcc($bcc);
179    
180    if ($evt)
181    {
182      if ($sent == count($to) + count($cc) + count($bcc))
183      {
184        $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
185      }
186      elseif ($sent > 0)
187      {
188        $evt->setResult(Swift_Events_SendEvent::RESULT_TENTATIVE);
189      }
190      else
191      {
192        $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED);
193      }
194      $evt->setFailedRecipients($failedRecipients);
195      $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
196    }
197    
198    $message->generateId(); //Make sure a new Message ID is used
199    
200    return $sent;
201  }
202  
203  /**
204   * Stop the SMTP connection.
205   */
206  public function stop()
207  {
208    if ($this->_started)
209    {
210      if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this))
211      {
212        $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStopped');
213        if ($evt->bubbleCancelled())
214        {
215          return;
216        }
217      }
218      
219      try
220      {
221        $this->executeCommand("QUIT\r\n", array(221));
222      }
223      catch (Swift_TransportException $e) {}
224      
225      try
226      {
227        $this->_buffer->terminate();
228      
229        if ($evt)
230        {
231          $this->_eventDispatcher->dispatchEvent($evt, 'transportStopped');
232        }
233      }
234      catch (Swift_TransportException $e)
235      {
236        $this->_throwException($e);
237      }
238    }
239    $this->_started = false;
240  }
241  
242  /**
243   * Register a plugin.
244   * 
245   * @param Swift_Events_EventListener $plugin
246   */
247  public function registerPlugin(Swift_Events_EventListener $plugin)
248  {
249    $this->_eventDispatcher->bindEventListener($plugin);
250  }
251  
252  /**
253   * Reset the current mail transaction.
254   */
255  public function reset()
256  {
257    $this->executeCommand("RSET\r\n", array(250));
258  }
259  
260  /**
261   * Get the IoBuffer where read/writes are occurring.
262   * 
263   * @return Swift_Transport_IoBuffer
264   */
265  public function getBuffer()
266  {
267    return $this->_buffer;
268  }
269  
270  /**
271   * Run a command against the buffer, expecting the given response codes.
272   * 
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.
275   * 
276   * @param string $command
277   * @param int[] $codes
278   * @param string[] &$failures
279   * @return string
280   */
281  public function executeCommand($command, $codes = array(), &$failures = null)
282  {
283    $failures = (array) $failures;
284    $seq = $this->_buffer->write($command);
285    $response = $this->_getFullResponse($seq);
286    if ($evt = $this->_eventDispatcher->createCommandEvent($this, $command, $codes))
287    {
288      $this->_eventDispatcher->dispatchEvent($evt, 'commandSent');
289    }
290    $this->_assertResponseCode($response, $codes);
291    return $response;
292  }
293  
294  // -- Protected methods
295  
296  /** Read the opening SMTP greeting */
297  protected function _readGreeting()
298  {
299    $this->_assertResponseCode($this->_getFullResponse(0), array(220));
300  }
301  
302  /** Send the HELO welcome */
303  protected function _doHeloCommand()
304  {
305    $this->executeCommand(
306      sprintf("HELO %s\r\n", $this->_domain), array(250)
307      );
308  }
309  
310  /** Send the MAIL FROM command */
311  protected function _doMailFromCommand($address)
312  {
313    $this->executeCommand(
314      sprintf("MAIL FROM: <%s>\r\n", $address), array(250)
315      );
316  }
317  
318  /** Send the RCPT TO command */
319  protected function _doRcptToCommand($address)
320  {
321    $this->executeCommand(
322      sprintf("RCPT TO: <%s>\r\n", $address), array(250, 251, 252)
323      );
324  }
325  
326  /** Send the DATA command */
327  protected function _doDataCommand()
328  {
329    $this->executeCommand("DATA\r\n", array(354));
330  }
331  
332  /** Stream the contents of the message over the buffer */
333  protected function _streamMessage(Swift_Mime_Message $message)
334  {
335    $this->_buffer->setWriteTranslations(array("\r\n." => "\r\n.."));
336    try
337    {
338      $message->toByteStream($this->_buffer);
339      $this->_buffer->flushBuffers();
340    }
341    catch (Swift_TransportException $e)
342    {
343      $this->_throwException($e);
344    }
345    $this->_buffer->setWriteTranslations(array());
346    $this->executeCommand("\r\n.\r\n", array(250));
347  }
348  
349  /** Determine the best-use reverse path for this message */
350  protected function _getReversePath(Swift_Mime_Message $message)
351  {
352    $return = $message->getReturnPath();
353    $sender = $message->getSender();
354    $from = $message->getFrom();
355    $path = null;
356    if (!empty($return))
357    {
358      $path = $return;
359    }
360    elseif (!empty($sender))
361    {
362      // Don't use array_keys
363      reset($sender); // Reset Pointer to first pos
364      $path = key($sender); // Get key
365    }
366    elseif (!empty($from))
367    {
368      reset($from); // Reset Pointer to first pos
369      $path = key($from); // Get key
370    }
371    return $path;
372  }
373  
374  /** Throw a TransportException, first sending it to any listeners */
375  protected function _throwException(Swift_TransportException $e)
376  {
377    if ($evt = $this->_eventDispatcher->createTransportExceptionEvent($this, $e))
378    {
379      $this->_eventDispatcher->dispatchEvent($evt, 'exceptionThrown');
380      if (!$evt->bubbleCancelled())
381      {
382        throw $e;
383      }
384    }
385    else
386    {
387      throw $e;
388    }
389  }
390  
391  /** Throws an Exception if a response code is incorrect */
392  protected function _assertResponseCode($response, $wanted)
393  {
394    list($code, $separator, $text) = sscanf($response, '%3d%[ -]%s');
395    $valid = (empty($wanted) || in_array($code, $wanted));
396    
397    if ($evt = $this->_eventDispatcher->createResponseEvent($this, $response,
398      $valid))
399    {
400      $this->_eventDispatcher->dispatchEvent($evt, 'responseReceived');
401    }
402    
403    if (!$valid)
404    {
405      $this->_throwException(
406        new Swift_TransportException(
407          'Expected response code ' . implode('/', $wanted) . ' but got code ' .
408          '"' . $code . '", with message "' . $response . '"'
409          )
410        );
411    }
412  }
413  
414  /** Get an entire multi-line response using its sequence number */
415  protected function _getFullResponse($seq)
416  {
417    $response = '';
418    try
419    {
420      do
421      {
422        $line = $this->_buffer->readLine($seq);
423        $response .= $line;
424      }
425      while (null !== $line && false !== $line && ' ' != $line{3});
426    }
427    catch (Swift_TransportException $e)
428    {
429      $this->_throwException($e);
430    }
431    return $response;
432  }
433  
434  // -- Private methods
435  
436  /** Send an email to the given recipients from the given reverse path */
437  private function _doMailTransaction($message, $reversePath,
438    array $recipients, array &$failedRecipients)
439  {
440    $sent = 0;
441    $this->_doMailFromCommand($reversePath);
442    foreach ($recipients as $forwardPath)
443    {
444      try
445      {
446        $this->_doRcptToCommand($forwardPath);
447        $sent++;
448      }
449      catch (Swift_TransportException $e)
450      {
451        $failedRecipients[] = $forwardPath;
452      }
453    }
454    
455    if ($sent != 0)
456    {
457      $this->_doDataCommand();
458      $this->_streamMessage($message);
459    }
460    else
461    {
462      $this->reset();
463    }
464    
465    return $sent;
466  }
467  
468  /** Send a message to the given To: recipients */
469  private function _sendTo(Swift_Mime_Message $message, $reversePath,
470    array $to, array &$failedRecipients)
471  {
472    if (empty($to))
473    {
474      return 0;
475    }
476    return $this->_doMailTransaction($message, $reversePath, array_keys($to),
477      $failedRecipients);
478  }
479  
480  /** Send a message to the given Cc: recipients */
481  private function _sendCc(Swift_Mime_Message $message, $reversePath,
482    array $cc, array &$failedRecipients)
483  {
484    if (empty($cc))
485    {
486      return 0;
487    }
488    return $this->_doMailTransaction($message, $reversePath, array_keys($cc),
489      $failedRecipients);
490  }
491  
492  /** Send a message to all Bcc: recipients */
493  private function _sendBcc(Swift_Mime_Message $message, $reversePath,
494    array $bcc, array &$failedRecipients)
495  {
496    $sent = 0;
497    foreach ($bcc as $forwardPath => $name)
498    {
499      $message->setBcc(array($forwardPath => $name));
500      $sent += $this->_doMailTransaction(
501        $message, $reversePath, array($forwardPath), $failedRecipients
502        );
503    }
504    return $sent;
505  }
506  
507  /** Try to determine the hostname of the server this is run on */
508  private function _lookupHostname()
509  {
510    if (!empty($_SERVER['SERVER_NAME'])
511      && $this->_isFqdn($_SERVER['SERVER_NAME']))
512    {
513      $this->_domain = $_SERVER['SERVER_NAME'];
514    }
515    elseif (!empty($_SERVER['SERVER_ADDR']))
516    {
517      $this->_domain = sprintf('[%s]', $_SERVER['SERVER_ADDR']);
518    }
519  }
520  
521  /** Determine is the $hostname is a fully-qualified name */
522  private function _isFqdn($hostname)
523  {
524    //We could do a really thorough check, but there's really no point
525    if (false !== $dotPos = strpos($hostname, '.'))
526    {
527      return ($dotPos > 0) && ($dotPos != strlen($hostname) - 1);
528    }
529    else
530    {
531      return false;
532    }
533  }
534  
535  /**
536   * Destructor.
537   */
538  public function __destruct()
539  {
540    $this->stop();
541  }
542  
543}