<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

//@require 'Swift/Events/TransportChangeListener.php';
//@require 'Swift/Events/TransportChangeEvent.php';

/**
 * Makes sure a connection to a POP3 host has been established prior to connecting to SMTP.
 * 
 * @package Swift
 * @subpackage Plugins
 * 
 * @author Chris Corbyn
 */
class Swift_Plugins_PopBeforeSmtpPlugin
  implements Swift_Events_TransportChangeListener, Swift_Plugins_Pop_Pop3Connection
{
  
  /** A delegate connection to use (mostly a test hook) */
  private $_connection;
  
  /** Hostname of the POP3 server */
  private $_host;
  
  /** Port number to connect on */
  private $_port;
  
  /** Encryption type to use (if any) */
  private $_crypto;
  
  /** Username to use (if any) */
  private $_username;
  
  /** Password to use (if any) */
  private $_password;
  
  /** Established connection via TCP socket */
  private $_socket;
  
  /** Connect timeout in seconds */
  private $_timeout = 10;
  
  /** SMTP Transport to bind to */
  private $_transport;
  
  /**
   * Create a new PopBeforeSmtpPlugin for $host and $port.
   * 
   * @param string $host
   * @param int $port
   * @param string $cypto as "tls" or "ssl"
   */
  public function __construct($host, $port = 110, $crypto = null)
  {
    $this->_host = $host;
    $this->_port = $port;
    $this->_crypto = $crypto;
  }
  
  /**
   * Create a new PopBeforeSmtpPlugin for $host and $port.
   * 
   * @param string $host
   * @param int $port
   * @param string $cypto as "tls" or "ssl"
   * 
   * @return Swift_Plugins_PopBeforeSmtpPlugin
   */
  public static function newInstance($host, $port = 110, $crypto = null)
  {
    return new self($host, $port, $crypto);
  }
  
  /**
   * Set a Pop3Connection to delegate to instead of connecting directly.
   * 
   * @param Swift_Plugins_Pop_Pop3Connection $connection
   */
  public function setConnection(Swift_Plugins_Pop_Pop3Connection $connection)
  {
    $this->_connection = $connection;
    return $this;
  }
  
  /**
   * Bind this plugin to a specific SMTP transport instance.
   * 
   * @param Swift_Transport
   */
  public function bindSmtp(Swift_Transport $smtp)
  {
    $this->_transport = $smtp;
  }
  
  /**
   * Set the connection timeout in seconds (default 10).
   * 
   * @param int $timeout
   */
  public function setTimeout($timeout)
  {
    $this->_timeout = (int) $timeout;
    return $this;
  }
  
  /**
   * Set the username to use when connecting (if needed).
   * 
   * @param string $username
   */
  public function setUsername($username)
  {
    $this->_username = $username;
    return $this;
  }
  
  /**
   * Set the password to use when connecting (if needed).
   * 
   * @param string $password
   */
  public function setPassword($password)
  {
    $this->_password = $password;
    return $this;
  }
  
  /**
   * Connect to the POP3 host and authenticate.
   * 
   * @throws Swift_Plugins_Pop_Pop3Exception if connection fails
   */
  public function connect()
  {
    if (isset($this->_connection))
    {
      $this->_connection->connect();
    }
    else
    {
      if (!isset($this->_socket))
      {
        if (!$socket = fsockopen(
          $this->_getHostString(), $this->_port, $errno, $errstr, $this->_timeout))
        {
          throw new Swift_Plugins_Pop_Pop3Exception(
            sprintf('Failed to connect to POP3 host [%s]: %s', $this->_host, $errstr)
          );
        }
        $this->_socket = $socket;
        
        if (false === $greeting = fgets($this->_socket))
        {
          throw new Swift_Plugins_Pop_Pop3Exception(
            sprintf('Failed to connect to POP3 host [%s]', trim($greeting))
          );
        }
        
        $this->_assertOk($greeting);
        
        if ($this->_username)
        {
          $this->_command(sprintf("USER %s\r\n", $this->_username));
          $this->_command(sprintf("PASS %s\r\n", $this->_password));
        }
      }
    }
  }
  
  /**
   * Disconnect from the POP3 host.
   */
  public function disconnect()
  {
    if (isset($this->_connection))
    {
      $this->_connection->disconnect();
    }
    else
    {
      $this->_command("QUIT\r\n");
      if (!fclose($this->_socket))
      {
        throw new Swift_Plugins_Pop_Pop3Exception(
          sprintf('POP3 host [%s] connection could not be stopped', $this->_host)
        );
      }
      $this->_socket = null;
    }
  }
  
  /**
   * Invoked just before a Transport is started.
   * 
   * @param Swift_Events_TransportChangeEvent $evt
   */
  public function beforeTransportStarted(Swift_Events_TransportChangeEvent $evt)
  {
    if (isset($this->_transport))
    {
      if ($this->_transport !== $evt->getTransport())
      {
        return;
      }
    }
    
    $this->connect();
    $this->disconnect();
  }
  
  /**
   * Not used.
   */
  public function transportStarted(Swift_Events_TransportChangeEvent $evt)
  {
  }
  
  /**
   * Not used.
   */
  public function beforeTransportStopped(Swift_Events_TransportChangeEvent $evt)
  {
  }
  
  /**
   * Not used.
   */
  public function transportStopped(Swift_Events_TransportChangeEvent $evt)
  {
  }
  
  // -- Private Methods
  
  private function _command($command)
  {
    if (!fwrite($this->_socket, $command))
    {
      throw new Swift_Plugins_Pop_Pop3Exception(
        sprintf('Failed to write command [%s] to POP3 host', trim($command))
      );
    }
    
    if (false === $response = fgets($this->_socket))
    {
      throw new Swift_Plugins_Pop_Pop3Exception(
        sprintf('Failed to read from POP3 host after command [%s]', trim($command))
      );
    }
    
    $this->_assertOk($response);
    
    return $response;
  }
  
  private function _assertOk($response)
  {
    if (substr($response, 0, 3) != '+OK')
    {
      throw new Swift_Plugins_Pop_Pop3Exception(
        sprintf('POP3 command failed [%s]', trim($response))
      );
    }
  }
  
  private function _getHostString()
  {
    $host = $this->_host;
    switch (strtolower($this->_crypto))
    {
      case 'ssl':
        $host = 'ssl://' . $host;
        break;
      
      case 'tls':
        $host = 'tls://' . $host;
        break;
    }
    return $host;
  }
  
}
