code:
<?php
/**
 * Phergie
 *
 * PHP version 5
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.
 * It is also available through the world-wide-web at this URL:
 * http://phergie.org/license
 *
 * @category  Phergie
 * @package   Phergie
 * @author    Phergie Development Team <team@phergie.org>
 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
 * @license   http://phergie.org/license New BSD License
 * @link      http://pear.phergie.org/package/Phergie
 */

/**
 * Reads from and writes to PHP configuration files and provides access to
 * the settings they contain.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
class Phergie_Config implements ArrayAccess
{
    /**
     * Mapping of configuration file paths to an array of names of settings
     * they contain
     *
     * @var array
     */
    protected $files = array();

    /**
     * Mapping of setting names to their current corresponding values
     *
     * @var array
     */
    protected $settings = array();

    /**
     * Includes a specified PHP configuration file and incorporates its
     * return value (which should be an associative array) into the current
     * configuration settings.
     *
     * @param string $file Path to the file to read
     *
     * @return Phergie_Config Provides a fluent interface
     * @throws Phergie_Config_Exception
     */
    public function read($file)
    {
        if (!(strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'
            && file_exists($file))
            && !is_executable($file)
        ) {
            throw new Phergie_Config_Exception(
                'Path "' . $file . '" does not reference an executable file',
                Phergie_Config_Exception::ERR_FILE_NOT_EXECUTABLE
            );
        }

        $settings = include $file;
        if (!is_array($settings)) {
            throw new Phergie_Config_Exception(
                'File "' . $file . '" does not return an array',
                Phergie_Config_Exception::ERR_ARRAY_NOT_RETURNED
            );
        }

        $this->files[$file] = array_keys($settings);
        $this->settings += $settings;

        return $this;
    }

    /**
     * Merges an associative array of configuration setting values into the
     * current configuration settings.
     *
     * @param array $settings Associative array of configuration setting
     *        values keyed by setting name
     *
     * @return Phergie_Config Provides a fluent interface
     */
    public function readArray(array $settings)
    {
        $this->settings += $settings;

        return $this;
    }

    /**
     * Writes the values of the current configuration settings back to their
     * originating files.
     *
     * @return Phergie_Config Provides a fluent interface
     */
    public function write()
    {
        foreach ($this->files as $file => &$settings) {
            $values = array();
            foreach ($settings as $setting) {
                $values[$setting] = $this->settings[$setting];
            }
            $source = '<?php' . PHP_EOL . PHP_EOL .
                'return ' . var_export($value, true) . ';';
            file_put_contents($file, $source);
        }
    }

    /**
     * Checks to see if a configuration setting is assigned a value.
     *
     * @param string $offset Configuration setting name
     *
     * @return bool TRUE if the setting has a value, FALSE otherwise
     * @see ArrayAccess::offsetExists()
     */
    public function offsetExists($offset)
    {
        return isset($this->settings[$offset]);
    }

    /**
     * Returns the value of a configuration setting.
     *
     * @param string $offset Configuration setting name
     *
     * @return mixed Configuration setting value or NULL if it is not
     *         assigned a value
     * @see ArrayAccess::offsetGet()
     */
    public function offsetGet($offset)
    {
        if (isset($this->settings[$offset])) {
            $value = &$this->settings[$offset];
        } else {
            $value = null;
        }

        return $value;
    }

    /**
     * Sets the value of a configuration setting.
     *
     * @param string $offset Configuration setting name
     * @param mixed  $value  New setting value
     *
     * @return void
     * @see ArrayAccess::offsetSet()
     */
    public function offsetSet($offset, $value)
    {
        $this->settings[$offset] = $value;
    }

    /**
     * Removes the value set for a configuration setting.
     *
     * @param string $offset Configuration setting name
     *
     * @return void
     * @see ArrayAccess::offsetUnset()
     */
    public function offsetUnset($offset)
    {
        unset($this->settings[$offset]);

        foreach ($this->files as $file => $settings) {
            $key = array_search($offset, $settings);
            if ($key !== false) {
                unset($this->files[$file][$key]);
            }
        }
    }
}


code:
<?php
/**
 * Phergie
 *
 * PHP version 5
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.
 * It is also available through the world-wide-web at this URL:
 * http://phergie.org/license
 *
 * @category  Phergie
 * @package   Phergie
 * @author    Phergie Development Team <team@phergie.org>
 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
 * @license   http://phergie.org/license New BSD License
 * @link      http://pear.phergie.org/package/Phergie
 */

/**
 * Exception related to configuration.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
class Phergie_Config_Exception extends Phergie_Exception
{
    /**
     * Error indicating that an attempt was made to read a configuration
     * file that could not be executed
     */
    const ERR_FILE_NOT_EXECUTABLE = 1;

    /**
     * Error indicating that a read configuration file does not return an
     * array
     */
    const ERR_ARRAY_NOT_RETURNED = 2;
}

license: mit


code:
<?php
/**
 * Phergie
 *
 * PHP version 5
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.
 * It is also available through the world-wide-web at this URL:
 * http://phergie.org/license
 *
 * @category  Phergie
 * @package   Phergie
 * @author    Phergie Development Team <team@phergie.org>
 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
 * @license   http://phergie.org/license New BSD License
 * @link      http://pear.phergie.org/package/Phergie
 */

/**
 * Data structure for connection metadata.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
class Phergie_Connection
{
    /**
     * Host to which the client will connect
     *
     * @var string
     */
    protected $host;

    /**
     * Port on which the client will connect, defaults to the standard IRC
     * port
     *
     * @var int
     */
    protected $port;

    /**
     * Transport for the connection, defaults to tcp but can be set to ssl
     * or variations thereof to connect over SSL
     *
     * @var string
     */
    protected $transport;

    /**
     * Encoding method for the connection, defaults to ISO-8859-1 but can
     * be set to UTF8 if necessary
     *
     * @var strng
     */
    protected $encoding;

    /**
     * Nick that the client will use
     *
     * @var string
     */
    protected $nick;

    /**
     * Username that the client will use
     *
     * @var string
     */
    protected $username;

    /**
     * Realname that the client will use
     *
     * @var string
     */
    protected $realname;

    /**
     * Password that the client will use
     *
     * @var string
     */
    protected $password;

    /**
     * Hostmask for the connection
     *
     * @var Phergie_Hostmask
     */
    protected $hostmask;

    /**
     * Constructor to initialize instance properties.
     *
     * @param array $options Optional associative array of property values
     *        to initialize
     *
     * @return void
     */
    public function __construct(array $options = array())
    {
        $this->transport = 'tcp';
        $this->encoding = 'ISO-8859-1';
        // @note this may need changed to something different, for broader support.
        // @note also may need to make use of http://us.php.net/manual/en/function.stream-encoding.php

        $this->setOptions($options);
    }

    /**
     * Emits an error related to a required connection setting does not have
     * value set for it.
     *
     * @param string $setting Name of the setting
     *
     * @return void
     */
    protected function checkSetting($setting)
    {
        if (empty($this->$setting)) {
            throw new Phergie_Connection_Exception(
                'Required connection setting "' . $setting . '" missing',
                Phergie_Connection_Exception::ERR_REQUIRED_SETTING_MISSING
            );
        }
    }

    /**
     * Returns a hostmask that uniquely identifies the connection.
     *
     * @return string
     */
    public function getHostmask()
    {
        if (empty($this->hostmask)) {
            $this->hostmask = new Phergie_Hostmask(
                $this->getNick(),
                $this->getUsername(),
                $this->getHost()
            );
        }

        return $this->hostmask;
    }

    /**
     * Sets the host to which the client will connect.
     *
     * @param string $host Hostname
     *
     * @return Phergie_Connection Provides a fluent interface
     */
    public function setHost($host)
    {
        if (empty($this->host)) {
            $this->host = (string) $host;
        }

        return $this;
    }

    /**
     * Returns the host to which the client will connect if it is set or
     * emits an error if it is not set.
     *
     * @return string
     */
    public function getHost()
    {
        $this->checkSetting('host');

        return $this->host;
    }

    /**
     * Sets the port on which the client will connect.
     *
     * @param int $port Port
     *
     * @return Phergie_Connection Provides a fluent interface
     */
    public function setPort($port)
    {
        if (empty($this->port)) {
            $this->port = (int) $port;
        }

        return $this;
    }

    /**
     * Returns the port on which the client will connect.
     *
     * @return int
     */
    public function getPort()
    {
        if (empty($this->port)) {
            $this->port = 6667;
        }

        return $this->port;
    }

    /**
     * Sets the transport for the connection to use.
     *
     * @param string $transport Transport (ex: tcp, ssl, etc.)
     *
     * @return Phergie_Connection Provides a fluent interface
     */
    public function setTransport($transport)
    {
        $this->transport = (string) $transport;

        if (!in_array($this->transport, stream_get_transports())) {
            throw new Phergie_Connection_Exception(
                'Transport ' . $this->transport . ' is not supported',
                Phergie_Connection_Exception::ERR_TRANSPORT_NOT_SUPPORTED
            );
        }

        return $this;
    }

    /**
     * Returns the transport in use by the connection.
     *
     * @return string Transport (ex: tcp, ssl, etc.)
     */
    public function getTransport()
    {
        return $this->transport;
    }

    /**
     * Sets the encoding for the connection to use.
     *
     * @param string $encoding Encoding to use (ex: ASCII, ISO-8859-1, UTF8, etc.)
     *
     * @return Phergie_Connection Provides a fluent interface
     */
    public function setEncoding($encoding)
    {
        $this->encoding = (string) $encoding;

        if (!in_array($this->encoding, mb_list_encodings())) {
            throw new Phergie_Connection_Exception(
                'Encoding ' . $this->encoding . ' is not supported',
                Phergie_Connection_Exception::ERR_ENCODING_NOT_SUPPORTED
            );
        }

        return $this;
    }

    /**
     * Returns the encoding in use by the connection.
     *
     * @return string Encoding (ex: ASCII, ISO-8859-1, UTF8, etc.)
     */
    public function getEncoding()
    {
        return $this->encoding;
    }

    /**
     * Sets the nick that the client will use.
     *
     * @param string $nick Nickname
     *
     * @return Phergie_Connection Provides a fluent interface
     */
    public function setNick($nick)
    {
        if (empty($this->nick)) {
            $this->nick = (string) $nick;
        }

        return $this;
    }

    /**
     * Returns the nick that the client will use.
     *
     * @return string
     */
    public function getNick()
    {
        $this->checkSetting('nick');

        return $this->nick;
    }

    /**
     * Sets the username that the client will use.
     *
     * @param string $username Username
     *
     * @return Phergie_Connection Provides a fluent interface
     */
    public function setUsername($username)
    {
        if (empty($this->username)) {
            $this->username = (string) $username;
        }

        return $this;
    }

    /**
     * Returns the username that the client will use.
     *
     * @return string
     */
    public function getUsername()
    {
        $this->checkSetting('username');

        return $this->username;
    }

    /**
     * Sets the realname that the client will use.
     *
     * @param string $realname Real name
     *
     * @return Phergie_Connection Provides a fluent interface
     */
    public function setRealname($realname)
    {
        if (empty($this->realname)) {
            $this->realname = (string) $realname;
        }

        return $this;
    }

    /**
     * Returns the realname that the client will use.
     *
     * @return string
     */
    public function getRealname()
    {
        $this->checkSetting('realname');

        return $this->realname;
    }

    /**
     * Sets the password that the client will use.
     *
     * @param string $password Password
     *
     * @return Phergie_Connection Provides a fluent interface
     */
    public function setPassword($password)
    {
        if (empty($this->password)) {
            $this->password = (string) $password;
        }

        return $this;
    }

    /**
     * Returns the password that the client will use.
     *
     * @return string
     */
    public function getPassword()
    {
        return $this->password;
    }

    /**
     * Sets multiple connection settings using an array.
     *
     * @param array $options Associative array of setting names mapped to
     *        corresponding values
     *
     * @return Phergie_Connection Provides a fluent interface
     */
    public function setOptions(array $options)
    {
        foreach ($options as $option => $value) {
            $method = 'set' . ucfirst($option);
            if (method_exists($this, $method)) {
                $this->$method($value);
            }
        }
    }
}


code:
<?php
/**
 * Phergie
 *
 * PHP version 5
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.
 * It is also available through the world-wide-web at this URL:
 * http://phergie.org/license
 *
 * @category  Phergie
 * @package   Phergie
 * @author    Phergie Development Team <team@phergie.org>
 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
 * @license   http://phergie.org/license New BSD License
 * @link      http://pear.phergie.org/package/Phergie
 */

/**
 * Exception related to a connection to an IRC server.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
class Phergie_Connection_Exception extends Phergie_Exception
{
    /**
     * Error indicating that an operation was attempted requiring a value
     * for a specific configuration setting, but none was set
     */
    const ERR_REQUIRED_SETTING_MISSING = 1;

    /**
     * Error indicating that a connection is configured to use a transport,
     * but that transport is not supported by the current PHP installation
     */
    const ERR_TRANSPORT_NOT_SUPPORTED = 2;

    /**
     * Error indicating that a connection is configured to use an encoding,
     * but that encoding is not supported by the current PHP installation
     */
    const ERR_ENCODING_NOT_SUPPORTED = 3;
}


code:
<?php
/**
 * Phergie
 *
 * PHP version 5
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.
 * It is also available through the world-wide-web at this URL:
 * http://phergie.org/license
 *
 * @category  Phergie
 * @package   Phergie
 * @author    Phergie Development Team <team@phergie.org>
 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
 * @license   http://phergie.org/license New BSD License
 * @link      http://pear.phergie.org/package/Phergie
 */

/**
 * Handles connections initiated by the bot.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
class Phergie_Connection_Handler implements Countable, IteratorAggregate
{
    /**
     * Map of connections indexed by hostmask
     *
     * @var array
     */
    protected $connections;

    /**
     * Constructor to initialize storage for connections.
     *
     * @return void
     */
    public function __construct()
    {
        $this->connections = array();
    }

    /**
     * Adds a connection to the connection list.
     *
     * @param Phergie_Connection $connection Connection to add
     *
     * @return Phergie_Connection_Handler Provides a fluent interface
     */
    public function addConnection(Phergie_Connection $connection)
    {
        $this->connections[(string) $connection->getHostmask()] = $connection;
        return $this;
    }

    /**
     * Removes a connection from the connection list.
     *
     * @param Phergie_Connection|string $connection Instance or hostmask for
     *        the connection to remove
     *
     * @return Phergie_Connection_Handler Provides a fluent interface
     */
    public function removeConnection($connection)
    {
        if ($connection instanceof Phergie_Connection) {
            $hostmask = (string) $connection->getHostmask();
        } elseif (is_string($connection)
            && isset($this->connections[$connection])) {
            $hostmask = $connection;
        } else {
            return $this;
        }
        unset($this->connections[$hostmask]);
        return $this;
    }

    /**
     * Returns the number of connections in the list.
     *
     * @return int Number of connections
     */
    public function count()
    {
        return count($this->connections);
    }

    /**
     * Returns an iterator for the connection list.
     *
     * @return ArrayIterator
     */
    public function getIterator()
    {
        return new ArrayIterator($this->connections);
    }

    /**
     * Returns a list of specified connection objects.
     *
     * @param array|string $keys One or more hostmasks identifying the
     *        connections to return
     *
     * @return array List of Phergie_Connection objects corresponding to the
     *         specified hostmask(s)
     */
    public function getConnections($keys)
    {
        $connections = array();

        if (!is_array($keys)) {
            $keys = array($keys);
        }

        foreach ($keys as $key) {
            if (isset($this->connections[$key])) {
                $connections[] = $this->connections[$key];
            }
        }

        return $connections;
    }
}


code:
<?php
/**
 * Phergie
 *
 * PHP version 5
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.
 * It is also available through the world-wide-web at this URL:
 * http://phergie.org/license
 *
 * @category  Phergie
 * @package   Phergie
 * @author    Phergie Development Team <team@phergie.org>
 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
 * @license   http://phergie.org/license New BSD License
 * @link      http://pear.phergie.org/package/Phergie
 */

/**
 * Base class for drivers which handle issuing client commands to the IRC
 * server and converting responses into usable data objects.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
abstract class Phergie_Driver_Abstract
{
    /**
     * Currently active connection
     *
     * @var Phergie_Connection
     */
    protected $connection;

    /**
     * Sets the currently active connection.
     *
     * @param Phergie_Connection $connection Active connection
     *
     * @return Phergie_Driver_Abstract Provides a fluent interface
     */
    public function setConnection(Phergie_Connection $connection)
    {
        $this->connection = $connection;

        return $this;
    }

    /**
     * Returns the currently active connection.
     *
     * @return Phergie_Connection
     * @throws Phergie_Driver_Exception
     */
    public function getConnection()
    {
        if (empty($this->connection)) {
            throw new Phergie_Driver_Exception(
                'Operation requires an active connection, but none is set',
                Phergie_Driver_Exception::ERR_NO_ACTIVE_CONNECTION
            );
        }

        return $this->connection;
    }

    /**
     * Returns an event if one has been received from the server.
     *
     * @return Phergie_Event_Interface|null Event instance if an event has
     *         been received, NULL otherwise
     */
    public abstract function getEvent();

    /**
     * Initiates a connection with the server.
     *
     * @return void
     */
    public abstract function doConnect();

    /**
     * Terminates the connection with the server.
     *
     * @param string $reason Reason for connection termination (optional)
     *
     * @return void
     * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_1_6
     */
    public abstract function doQuit($reason = null);

    /**
     * Joins a channel.
     *
     * @param string $channels Comma-delimited list of channels to join
     * @param string $keys     Optional comma-delimited list of channel keys
     *
     * @return void
     * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_1
     */
    public abstract function doJoin($channels, $keys = null);

    /**
     * Leaves a channel.
     *
     * @param string $channels Comma-delimited list of channels to leave
     *
     * @return void
     * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_2
     */
    public abstract function doPart($channels);

    /**
     * Invites a user to an invite-only channel.
     *
     * @param string $nick    Nick of the user to invite
     * @param string $channel Name of the channel
     *
     * @return void
     * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_7
     */
    public abstract function doInvite($nick, $channel);

    /**
     * Obtains a list of nicks of users in specified channels.
     *
     * @param string $channels Comma-delimited list of one or more channels
     *
     * @return void
     * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_5
     */
    public abstract function doNames($channels);

    /**
     * Obtains a list of channel names and topics.
     *
     * @param string $channels Comma-delimited list of one or more channels
     *                         to which the response should be restricted
     *                         (optional)
     *
     * @return void
     * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_6
     */
    public abstract function doList($channels = null);

    /**
     * Retrieves or changes a channel topic.
     *
     * @param string $channel Name of the channel
     * @param string $topic   New topic to assign (optional)
     *
     * @return void
     * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_4
     */
    public abstract function doTopic($channel, $topic = null);

    /**
     * Retrieves or changes a channel or user mode.
     *
     * @param string $target Channel name or user nick
     * @param string $mode   New mode to assign (optional)
     *
     * @return void
     * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_3
     */
    public abstract function doMode($target, $mode = null);

    /**
     * Changes the client nick.
     *
     * @param string $nick New nick to assign
     *
     * @return void
     * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_1_2
     */
    public abstract function doNick($nick);

    /**
     * Retrieves information about a nick.
     *
     * @param string $nick Nick
     *
     * @return void
     * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_5_2
     */
    public abstract function doWhois($nick);

    /**
     * Sends a message to a nick or channel.
     *
     * @param string $target Channel name or user nick
     * @param string $text   Text of the message to send
     *
     * @return void
     * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_4_1
     */
    public abstract function doPrivmsg($target, $text);

    /**
     * Sends a notice to a nick or channel.
     *
     * @param string $target Channel name or user nick
     * @param string $text   Text of the notice to send
     *
     * @return void
     * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_4_2
     */
    public abstract function doNotice($target, $text);

    /**
     * Kicks a user from a channel.
     *
     * @param string $nick    Nick of the user
     * @param string $channel Channel name
     * @param string $reason  Reason for the kick (optional)
     *
     * @return void
     * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_8
     */
    public abstract function doKick($nick, $channel, $reason = null);

    /**
     * Responds to a server test of client responsiveness.
     *
     * @param string $daemon Daemon from which the original request originates
     *
     * @return void
     * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_6_3
     */
    public abstract function doPong($daemon);

    /**
     * Sends a CTCP ACTION (/me) command to a nick or channel.
     *
     * @param string $target Channel name or user nick
     * @param string $text   Text of the action to perform
     *
     * @return void
     * @link http://www.invlogic.com/irc/ctcp.html#4.4
     */
    public abstract function doAction($target, $text);

    /**
     * Sends a CTCP PING request to a user.
     *
     * @param string $nick User nick
     * @param string $hash Hash to use in the handshake
     *
     * @return void
     * @link http://www.invlogic.com/irc/ctcp.html#4.2
     */
    public abstract function doPing($nick, $hash);

    /**
     * Sends a CTCP VERSION request or response to a user.
     *
     * @param string $nick    User nick
     * @param string $version Version string to send for a response
     *
     * @return void
     * @link http://www.invlogic.com/irc/ctcp.html#4.1
     */
    public abstract function doVersion($nick, $version = null);

    /**
     * Sends a CTCP TIME request to a user.
     *
     * @param string $nick User nick
     * @param string $time Time string to send for a response
     *
     * @return void
     * @link http://www.invlogic.com/irc/ctcp.html#4.6
     */
    public abstract function doTime($nick, $time = null);

    /**
     * Sends a CTCP FINGER request to a user.
     *
     * @param string $nick   User nick
     * @param string $finger Finger string to send for a response
     *
     * @return void
     * @link http://www.irchelp.org/irchelp/rfc/ctcpspec.html
     */
    public abstract function doFinger($nick, $finger = null);

    /**
     * Sends a raw command to the server.
     *
     * @param string $command Command string to send
     *
     * @return void
     */
    public abstract function doRaw($command);
}


code:
<?php
/**
 * Phergie
 *
 * PHP version 5
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.
 * It is also available through the world-wide-web at this URL:
 * http://phergie.org/license
 *
 * @category  Phergie
 * @package   Phergie
 * @author    Phergie Development Team <team@phergie.org>
 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
 * @license   http://phergie.org/license New BSD License
 * @link      http://pear.phergie.org/package/Phergie
 */

/**
 * Exception related to driver operations.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
class Phergie_Driver_Exception extends Phergie_Exception
{
    /**
     * Error indicating that an operation was requested requiring an active
     * connection before one had been set
     */
    const ERR_NO_ACTIVE_CONNECTION = 1;

    /**
     * Error indicating that an operation was requested requiring an active
     * connection where one had been set but not initiated
     */
    const ERR_NO_INITIATED_CONNECTION = 2;

    /**
     * Error indicating that an attempt to initiate a connection failed
     */
    const ERR_CONNECTION_ATTEMPT_FAILED = 3;

    /**
     * Error indicating that an attempt to send data via a connection failed
     */
    const ERR_CONNECTION_WRITE_FAILED = 4;

    /**
     * Error indicating that an attempt to read data via a connection failed
     */
    const ERR_CONNECTION_READ_FAILED = 5;
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Extends the Streams driver (Phergie_Driver_Streams) to give external access
 * to the socket resources and send method
 *
 * @category  Phergie
 * @package   Phergie_Driver_Statusnet
 * @author    Luke Fitzgerald <lw.fitzgerald@googlemail.com>
 * @copyright 2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

class Phergie_Driver_Statusnet extends Phergie_Driver_Streams {
    /**
     * Handles construction of command strings and their transmission to the
     * server.
     *
     * @param string       $command Command to send
     * @param string|array $args    Optional string or array of sequential
     *        arguments
     *
     * @return string Command string that was sent
     * @throws Phergie_Driver_Exception
     */
    public function send($command, $args = '') {
        return parent::send($command, $args);
    }

    public function forceQuit() {
        try {
            // Send a QUIT command to the server
            $this->send('QUIT', 'Reconnecting');
        } catch (Phergie_Driver_Exception $e){}

        // Terminate the socket connection
        fclose($this->socket);

        // Remove the socket from the internal socket list
        unset($this->sockets[(string) $this->getConnection()->getHostmask()]);
    }

    /**
    * Returns the array of sockets
    *
    * @return array Array of socket resources
    */
    public function getSockets() {
        return $this->sockets;
    }
}


code:
<?php
/**
 * Phergie
 *
 * PHP version 5
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.
 * It is also available through the world-wide-web at this URL:
 * http://phergie.org/license
 *
 * @category  Phergie
 * @package   Phergie
 * @author    Phergie Development Team <team@phergie.org>
 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
 * @license   http://phergie.org/license New BSD License
 * @link      http://pear.phergie.org/package/Phergie
 */

/**
 * Driver that uses the sockets wrapper of the streams extension for
 * communicating with the server and handles formatting and parsing of
 * events using PHP.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
class Phergie_Driver_Streams extends Phergie_Driver_Abstract
{
    /**
     * Socket handlers
     *
     * @var array
     */
    protected $sockets = array();

    /**
     * Reference to the currently active socket handler
     *
     * @var resource
     */
    protected $socket;

    /**
     * Amount of time in seconds to wait to receive an event each time the
     * socket is polled
     *
     * @var float
     */
    protected $timeout = 0.1;

    /**
     * Handles construction of command strings and their transmission to the
     * server.
     *
     * @param string       $command Command to send
     * @param string|array $args    Optional string or array of sequential
     *        arguments
     *
     * @return string Command string that was sent
     * @throws Phergie_Driver_Exception
     */
    protected function send($command, $args = '')
    {
        $connection = $this->getConnection();
        $encoding = $connection->getEncoding();

        // Require an open socket connection to continue
        if (empty($this->socket)) {
            throw new Phergie_Driver_Exception(
                'doConnect() must be called first',
                Phergie_Driver_Exception::ERR_NO_INITIATED_CONNECTION
            );
        }

        // Add the command
        $buffer = strtoupper($command);

        // Add arguments
        if (!empty($args)) {

            // Apply formatting if arguments are passed in as an array
            if (is_array($args)) {
                $end = count($args) - 1;
                $args[$end] = ':' . $args[$end];
                $args = implode(' ', $args);
            } else {
                $args = ':' . $args;
            }

            $buffer .= ' ' . $args;
        }

        // Transmit the command over the socket connection
        $attempts = $written = 0;
        $temp = $buffer . "\r\n";
        $is_multibyte = !substr($encoding, 0, 8) === 'ISO-8859' && $encoding !== 'ASCII' && $encoding !== 'CP1252';
        $length = ($is_multibyte) ? mb_strlen($buffer, '8bit') : strlen($buffer);
        while (true) {
            $written += (int) fwrite($this->socket, $temp);
            if ($written < $length) {
                $temp = substr($temp, $written);
                $attempts++;
                if ($attempts == 3) {
                    throw new Phergie_Driver_Exception(
                        'Unable to write to socket',
                        Phergie_Driver_Exception::ERR_CONNECTION_WRITE_FAILED
                    );
                }
            } else {
                break;
            }
        }

        // Return the command string that was transmitted
        return $buffer;
    }

    /**
     * Overrides the parent class to set the currently active socket handler
     * when the active connection is changed.
     *
     * @param Phergie_Connection $connection Active connection
     *
     * @return Phergie_Driver_Streams Provides a fluent interface
     */
    public function setConnection(Phergie_Connection $connection)
    {
        // Set the active socket handler
        $hostmask = (string) $connection->getHostmask();
        if (!empty($this->sockets[$hostmask])) {
            $this->socket = $this->sockets[$hostmask];
        }

        // Set the active connection
        return parent::setConnection($connection);
    }

    /**
     * Returns a list of hostmasks corresponding to sockets with data to read.
     *
     * @param int $sec  Length of time to wait for new data (seconds)
     * @param int $usec Length of time to wait for new data (microseconds)
     *
     * @return array List of hostmasks or an empty array if none were found
     *         to have data to read
     */
    public function getActiveReadSockets($sec = 0, $usec = 200000)
    {
        $read = $this->sockets;
        $write = null;
        $error = null;
        $active = array();

        if (count($this->sockets) > 0) {
            $number = stream_select($read, $write, $error, $sec, $usec);
            if ($number > 0) {
                foreach ($read as $item) {
                    $active[] = array_search($item, $this->sockets);
                }
            }
        }

        return $active;
    }

    /**
     * Sets the amount of time to wait for a new event each time the socket
     * is polled.
     *
     * @param float $timeout Amount of time in seconds
     *
     * @return Phergie_Driver_Streams Provides a fluent interface
     */
    public function setTimeout($timeout)
    {
        $timeout = (float) $timeout;
        if ($timeout) {
            $this->timeout = $timeout;
        }
        return $this;
    }

    /**
     * Returns the amount of time to wait for a new event each time the
     * socket is polled.
     *
     * @return float Amount of time in seconds
     */
    public function getTimeout()
    {
        return $this->timeout;
    }

    /**
     * Supporting method to parse event argument strings where the last
     * argument may contain a colon.
     *
     * @param string $args  Argument string to parse
     * @param int    $count Optional maximum number of arguments
     *
     * @return array Array of argument values
     */
    protected function parseArguments($args, $count = -1)
    {
        return preg_split('/ :?/S', $args, $count);
    }

    /**
     * Listens for an event on the current connection.
     *
     * @return Phergie_Event_Interface|null Event instance if an event was
     *         received, NULL otherwise
     */
    public function getEvent()
    {
        // Check the socket is still active
        if (feof($this->socket)) {
            throw new Phergie_Driver_Exception(
                'EOF detected on socket',
                Phergie_Driver_Exception::ERR_CONNECTION_READ_FAILED
            );
        }

        // Check for a new event on the current connection
        $buffer = fgets($this->socket, 512);

        // If no new event was found, return NULL
        if (empty($buffer)) {
            return null;
        }

        // Strip the trailing newline from the buffer
        $buffer = rtrim($buffer);

        // If the event is from the server...
        if (substr($buffer, 0, 1) != ':') {

            // Parse the command and arguments
            list($cmd, $args) = array_pad(explode(' ', $buffer, 2), 2, null);
            $hostmask = new Phergie_Hostmask(null, null, $this->connection->getHost());

        } else {
            // If the event could be from the server or a user...

            // Parse the server hostname or user hostmask, command, and arguments
            list($prefix, $cmd, $args)
                = array_pad(explode(' ', ltrim($buffer, ':'), 3), 3, null);
            if (strpos($prefix, '@') !== false) {
                $hostmask = Phergie_Hostmask::fromString($prefix);
            } else {
                $hostmask = new Phergie_Hostmask(null, null, $prefix);
            }
        }

        // Parse the event arguments depending on the event type
        $cmd = strtolower($cmd);
        switch ($cmd) {
        case 'names':
        case 'nick':
        case 'quit':
        case 'ping':
        case 'join':
        case 'error':
            $args = array(ltrim($args, ':'));
            break;

        case 'privmsg':
        case 'notice':
            $args = $this->parseArguments($args, 2);
            list($source, $ctcp) = $args;
            if (substr($ctcp, 0, 1) === "\001" && substr($ctcp, -1) === "\001") {
                $ctcp = substr($ctcp, 1, -1);
                $reply = ($cmd == 'notice');
                list($cmd, $args) = array_pad(explode(' ', $ctcp, 2), 2, null);
                $cmd = strtolower($cmd);
                switch ($cmd) {
                case 'version':
                case 'time':
                case 'finger':
                    if ($reply) {
                        $args = $ctcp;
                    }
                    break;
                case 'ping':
                    if ($reply) {
                        $cmd .= 'Response';
                    } else {
                        $cmd = 'ctcpPing';
                    }
                    break;
                case 'action':
                    $args = array($source, $args);
                    break;

                default:
                    $cmd = 'ctcp';
                    if ($reply) {
                        $cmd .= 'Response';
                    }
                    $args = array($source, $args);
                    break;
                }
            }
            break;

        case 'oper':
        case 'topic':
        case 'mode':
            $args = $this->parseArguments($args);
            break;

        case 'part':
        case 'kill':
        case 'invite':
            $args = $this->parseArguments($args, 2);
            break;

        case 'kick':
            $args = $this->parseArguments($args, 3);
            break;

        // Remove the target from responses
        default:
            $args = substr($args, strpos($args, ' ') + 1);
            break;
        }

        // Create, populate, and return an event object
        if (ctype_digit($cmd)) {
            $event = new Phergie_Event_Response;
            $event
                ->setCode($cmd)
                ->setDescription($args);
        } else {
            $event = new Phergie_Event_Request;
            $event
                ->setType($cmd)
                ->setArguments($args);
            if (isset($hostmask)) {
                $event->setHostmask($hostmask);
            }
        }
        $event->setRawData($buffer);
        return $event;
    }

    /**
     * Initiates a connection with the server.
     *
     * @return void
     */
    public function doConnect()
    {
        // Listen for input indefinitely
        set_time_limit(0);

        // Get connection information
        $connection = $this->getConnection();
        $hostname = $connection->getHost();
        $port = $connection->getPort();
        $password = $connection->getPassword();
        $username = $connection->getUsername();
        $nick = $connection->getNick();
        $realname = $connection->getRealname();
        $transport = $connection->getTransport();

        // Establish and configure the socket connection
        $remote = $transport . '://' . $hostname . ':' . $port;
        $this->socket = @stream_socket_client($remote, $errno, $errstr);
        if (!$this->socket) {
            throw new Phergie_Driver_Exception(
                'Unable to connect: socket error ' . $errno . ' ' . $errstr,
                Phergie_Driver_Exception::ERR_CONNECTION_ATTEMPT_FAILED
            );
        }

        $seconds = (int) $this->timeout;
        $microseconds = ($this->timeout - $seconds) * 1000000;
        stream_set_timeout($this->socket, $seconds, $microseconds);

        // Send the password if one is specified
        if (!empty($password)) {
            $this->send('PASS', $password);
        }

        // Send user information
        $this->send(
            'USER',
            array(
                $username,
                $hostname,
                $hostname,
                $realname
            )
        );

        $this->send('NICK', $nick);

        // Add the socket handler to the internal array for socket handlers
        $this->sockets[(string) $connection->getHostmask()] = $this->socket;
    }

    /**
     * Terminates the connection with the server.
     *
     * @param string $reason Reason for connection termination (optional)
     *
     * @return void
     */
    public function doQuit($reason = null)
    {
        // Send a QUIT command to the server
        $this->send('QUIT', $reason);

        // Terminate the socket connection
        fclose($this->socket);

        // Remove the socket from the internal socket list
        unset($this->sockets[(string) $this->getConnection()->getHostmask()]);
    }

    /**
     * Joins a channel.
     *
     * @param string $channels Comma-delimited list of channels to join
     * @param string $keys     Optional comma-delimited list of channel keys
     *
     * @return void
     */
    public function doJoin($channels, $keys = null)
    {
        $args = array($channels);

        if (!empty($keys)) {
            $args[] = $keys;
        }

        $this->send('JOIN', $args);
    }

    /**
     * Leaves a channel.
     *
     * @param string $channels Comma-delimited list of channels to leave
     *
     * @return void
     */
    public function doPart($channels)
    {
        $this->send('PART', $channels);
    }

    /**
     * Invites a user to an invite-only channel.
     *
     * @param string $nick    Nick of the user to invite
     * @param string $channel Name of the channel
     *
     * @return void
     */
    public function doInvite($nick, $channel)
    {
        $this->send('INVITE', array($nick, $channel));
    }

    /**
     * Obtains a list of nicks of usrs in currently joined channels.
     *
     * @param string $channels Comma-delimited list of one or more channels
     *
     * @return void
     */
    public function doNames($channels)
    {
        $this->send('NAMES', $channels);
    }

    /**
     * Obtains a list of channel names and topics.
     *
     * @param string $channels Comma-delimited list of one or more channels
     *                         to which the response should be restricted
     *                         (optional)
     *
     * @return void
     */
    public function doList($channels = null)
    {
        $this->send('LIST', $channels);
    }

    /**
     * Retrieves or changes a channel topic.
     *
     * @param string $channel Name of the channel
     * @param string $topic   New topic to assign (optional)
     *
     * @return void
     */
    public function doTopic($channel, $topic = null)
    {
        $args = array($channel);

        if (!empty($topic)) {
            $args[] = $topic;
        }

        $this->send('TOPIC', $args);
    }

    /**
     * Retrieves or changes a channel or user mode.
     *
     * @param string $target Channel name or user nick
     * @param string $mode   New mode to assign (optional)
     *
     * @return void
     */
    public function doMode($target, $mode = null)
    {
        $args = array($target);

        if (!empty($mode)) {
            $args[] = $mode;
        }

        $this->send('MODE', $args);
    }

    /**
     * Changes the client nick.
     *
     * @param string $nick New nick to assign
     *
     * @return void
     */
    public function doNick($nick)
    {
        $this->send('NICK', $nick);
    }

    /**
     * Retrieves information about a nick.
     *
     * @param string $nick Nick
     *
     * @return void
     */
    public function doWhois($nick)
    {
        $this->send('WHOIS', $nick);
    }

    /**
     * Sends a message to a nick or channel.
     *
     * @param string $target Channel name or user nick
     * @param string $text   Text of the message to send
     *
     * @return void
     */
    public function doPrivmsg($target, $text)
    {
        $this->send('PRIVMSG', array($target, $text));
    }

    /**
     * Sends a notice to a nick or channel.
     *
     * @param string $target Channel name or user nick
     * @param string $text   Text of the notice to send
     *
     * @return void
     */
    public function doNotice($target, $text)
    {
        $this->send('NOTICE', array($target, $text));
    }

    /**
     * Kicks a user from a channel.
     *
     * @param string $nick    Nick of the user
     * @param string $channel Channel name
     * @param string $reason  Reason for the kick (optional)
     *
     * @return void
     */
    public function doKick($nick, $channel, $reason = null)
    {
        $args = array($nick, $channel);

        if (!empty($reason)) {
            $args[] = $response;
        }

        $this->send('KICK', $args);
    }

    /**
     * Responds to a server test of client responsiveness.
     *
     * @param string $daemon Daemon from which the original request originates
     *
     * @return void
     */
    public function doPong($daemon)
    {
        $this->send('PONG', $daemon);
    }

    /**
     * Sends a CTCP ACTION (/me) command to a nick or channel.
     *
     * @param string $target Channel name or user nick
     * @param string $text   Text of the action to perform
     *
     * @return void
     */
    public function doAction($target, $text)
    {
        $buffer = rtrim('ACTION ' . $text);

        $this->doPrivmsg($target, chr(1) . $buffer . chr(1));
    }

    /**
     * Sends a CTCP response to a user.
     *
     * @param string       $nick    User nick
     * @param string       $command Command to send
     * @param string|array $args    String or array of sequential arguments
     *        (optional)
     *
     * @return void
     */
    protected function doCtcp($nick, $command, $args = null)
    {
        if (is_array($args)) {
            $args = implode(' ', $args);
        }

        $buffer = rtrim(strtoupper($command) . ' ' . $args);

        $this->doNotice($nick, chr(1) . $buffer . chr(1));
    }

    /**
     * Sends a CTCP PING request or response (they are identical) to a user.
     *
     * @param string $nick User nick
     * @param string $hash Hash to use in the handshake
     *
     * @return void
     */
    public function doPing($nick, $hash)
    {
        $this->doCtcp($nick, 'PING', $hash);
    }

    /**
     * Sends a CTCP VERSION request or response to a user.
     *
     * @param string $nick    User nick
     * @param string $version Version string to send for a response
     *
     * @return void
     */
    public function doVersion($nick, $version = null)
    {
        if ($version) {
            $this->doCtcp($nick, 'VERSION', $version);
        } else {
            $this->doCtcp($nick, 'VERSION');
        }
    }

    /**
     * Sends a CTCP TIME request to a user.
     *
     * @param string $nick User nick
     * @param string $time Time string to send for a response
     *
     * @return void
     */
    public function doTime($nick, $time = null)
    {
        if ($time) {
            $this->doCtcp($nick, 'TIME', $time);
        } else {
            $this->doCtcp($nick, 'TIME');
        }
    }

    /**
     * Sends a CTCP FINGER request to a user.
     *
     * @param string $nick   User nick
     * @param string $finger Finger string to send for a response
     *
     * @return void
     */
    public function doFinger($nick, $finger = null)
    {
        if ($finger) {
            $this->doCtcp($nick, 'FINGER', $finger);
        } else {
            $this->doCtcp($nick, 'FINGER');
        }
    }

    /**
     * Sends a raw command to the server.
     *
     * @param string $command Command string to send
     *
     * @return void
     */
    public function doRaw($command)
    {
        $this->send('RAW', $command);
    }
}


code:
<?php
/**
 * Phergie
 *
 * PHP version 5
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.
 * It is also available through the world-wide-web at this URL:
 * http://phergie.org/license
 *
 * @category  Phergie
 * @package   Phergie
 * @author    Phergie Development Team <team@phergie.org>
 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
 * @license   http://phergie.org/license New BSD License
 * @link      http://pear.phergie.org/package/Phergie
 */

/**
 * Base class for events.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
abstract class Phergie_Event_Abstract
{
    /**
     * Event type, used for determining the callback to execute in response
     *
     * @var string
     */
    protected $type;

    /**
     * Returns the event type.
     *
     * @return string
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Sets the event type.
     *
     * @param string $type Event type
     *
     * @return Phergie_Event_Abstract Implements a fluent interface
     */
    public function setType($type)
    {
        $this->type = (string) $type;
        return $this;
    }
}


code:
<?php
/**
 * Phergie
 *
 * PHP version 5
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.
 * It is also available through the world-wide-web at this URL:
 * http://phergie.org/license
 *
 * @category  Phergie
 * @package   Phergie
 * @author    Phergie Development Team <team@phergie.org>
 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
 * @license   http://phergie.org/license New BSD License
 * @link      http://pear.phergie.org/package/Phergie
 */

/**
 * Event originating from a plugin for the bot.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
class Phergie_Event_Command extends Phergie_Event_Request
{
    /**
     * Reference to the plugin instance that created the event
     *
     * @var Phergie_Plugin_Abstract
     */
    protected $plugin;

    /**
     * Stores a reference to the plugin instance that created the event.
     *
     * @param Phergie_Plugin_Abstract $plugin Plugin instance
     *
     * @return Phergie_Event_Command Provides a fluent interface
     */
    public function setPlugin(Phergie_Plugin_Abstract $plugin)
    {
        $this->plugin = $plugin;
        return $this;
    }

    /**
     * Returns a reference to the plugin instance that created the event.
     *
     * @return Phergie_Plugin_Abstract
     */
    public function getPlugin()
    {
        return $this->plugin;
    }
}


code:
<?php
/**
 * Phergie
 *
 * PHP version 5
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.
 * It is also available through the world-wide-web at this URL:
 * http://phergie.org/license
 *
 * @category  Phergie
 * @package   Phergie
 * @author    Phergie Development Team <team@phergie.org>
 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
 * @license   http://phergie.org/license New BSD License
 * @link      http://pear.phergie.org/package/Phergie
 */

/**
 * Exception related to outgoing events.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
class Phergie_Event_Exception extends Phergie_Exception
{
    /**
     * Error indicating that an attempt was made to create an event of an
     * unknown type
     */
    const ERR_UNKNOWN_EVENT_TYPE = 1;
}


code:
<?php
/**
 * Phergie
 *
 * PHP version 5
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.
 * It is also available through the world-wide-web at this URL:
 * http://phergie.org/license
 *
 * @category  Phergie
 * @package   Phergie
 * @author    Phergie Development Team <team@phergie.org>
 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
 * @license   http://phergie.org/license New BSD License
 * @link      http://pear.phergie.org/package/Phergie
 */

/**
 * Handles events initiated by plugins.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
class Phergie_Event_Handler implements IteratorAggregate, Countable
{
    /**
     * Current queue of events
     *
     * @var array
     */
    protected $events;

    /**
     * Constructor to initialize the event queue.
     *
     * @return void
     */
    public function __construct()
    {
        $this->events = array();
    }

    /**
     * Adds an event to the queue.
     *
     * @param Phergie_Plugin_Abstract $plugin Plugin originating the event
     * @param string                  $type   Event type, corresponding to a
     *        Phergie_Event_Command::TYPE_* constant
     * @param array                   $args   Optional event arguments
     *
     * @return Phergie_Event_Handler Provides a fluent interface
     */
    public function addEvent(Phergie_Plugin_Abstract $plugin, $type,
        array $args = array()
    ) {
        if (!defined('Phergie_Event_Command::TYPE_' . strtoupper($type))) {
            throw new Phergie_Event_Exception(
                'Unknown event type "' . $type . '"',
                Phergie_Event_Exception::ERR_UNKNOWN_EVENT_TYPE
            );
        }

        $event = new Phergie_Event_Command;
        $event
            ->setPlugin($plugin)
            ->setType($type)
            ->setArguments($args);

        $this->events[] = $event;

        return $this;
    }

    /**
     * Returns the current event queue.
     *
     * @return array Enumerated array of Phergie_Event_Command objects
     */
    public function getEvents()
    {
        return $this->events;
    }

    /**
     * Clears the event queue.
     *
     * @return Phergie_Event_Handler Provides a fluent interface
     */
    public function clearEvents()
    {
        $this->events = array();
        return $this;
    }

    /**
     * Replaces the current event queue with a given queue of events.
     *
     * @param array $events Ordered list of objects of the class
     *        Phergie_Event_Command
     *
     * @return Phergie_Event_Handler Provides a fluent interface
     */
    public function replaceEvents(array $events)
    {
        $this->events = $events;
        return $this;
    }

    /**
     * Returns whether an event of the given type exists in the queue.
     *
     * @param string $type Event type from Phergie_Event_Request::TYPE_*
     *        constants
     *
     * @return bool TRUE if an event of the specified type exists in the
     *         queue, FALSE otherwise
     */
    public function hasEventOfType($type)
    {
        foreach ($this->events as $event) {
            if ($event->getType() == $type) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns a list of events of a specified type.
     *
     * @param string $type Event type from Phergie_Event_Request::TYPE_*
     *        constants
     *
     * @return array Array containing event instances of the specified type
     *         or an empty array if no such events were found
     */
    public function getEventsOfType($type)
    {
        $events = array();
        foreach ($this->events as $event) {
            if ($event->getType() == $type) {
                $events[] = $event;
            }
        }
        return $events;
    }

    /**
     * Removes a single event from the event queue.
     *
     * @param Phergie_Event_Command $event Event to remove
     *
     * @return Phergie_Event_Handler Provides a fluent interface
     */
    public function removeEvent(Phergie_Event_Command $event)
    {
        $key = array_search($event, $this->events);
        if ($key !== false) {
            unset($this->events[$key]);
        }
        return $this;
    }

    /**
     * Returns an iterator for the current event queue.
     *
     * @return ArrayIterator
     */
    public function getIterator()
    {
        return new ArrayIterator($this->events);
    }

    /**
     * Returns the number of events in the event queue
     *
     * @return int number of queued events
     */
    public function count()
    {
        return count($this->events);
    }
}


code:
<?php
/**
 * Phergie
 *
 * PHP version 5
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.
 * It is also available through the world-wide-web at this URL:
 * http://phergie.org/license
 *
 * @category  Phergie
 * @package   Phergie
 * @author    Phergie Development Team <team@phergie.org>
 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
 * @license   http://phergie.org/license New BSD License
 * @link      http://pear.phergie.org/package/Phergie
 */

/**
 * Autonomous event originating from a user or the server.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 * @link     http://www.irchelp.org/irchelp/rfc/chapter4.html
 */
class Phergie_Event_Request
    extends Phergie_Event_Abstract
    implements ArrayAccess
{
    /**
     * Nick message event type
     */
    const TYPE_NICK = 'nick';

    /**
     * Whois message event type
     */
    const TYPE_WHOIS = 'whois';

    /**
     * Quit command event type
     */
    const TYPE_QUIT = 'quit';

    /**
     * Join message event type
     */
    const TYPE_JOIN = 'join';

    /**
     * Kick message event type
     */
    const TYPE_KICK = 'kick';

    /**
     * Part message event type
     */
    const TYPE_PART = 'part';

    /**
     * Invite message event type
     */
    const TYPE_INVITE = 'invite';

    /**
     * Mode message event type
     */
    const TYPE_MODE = 'mode';

    /**
     * Topic message event type
     */
    const TYPE_TOPIC = 'topic';

    /**
     * Private message command event type
     */
    const TYPE_PRIVMSG = 'privmsg';

    /**
     * Notice message event type
     */
    const TYPE_NOTICE = 'notice';

    /**
     * Pong message event type
     */
    const TYPE_PONG = 'pong';

    /**
     * CTCP ACTION command event type
     */
    const TYPE_ACTION = 'action';

    /**
     * CTCP PING command event type
     */
    const TYPE_PING = 'ping';

    /**
     * CTCP TIME command event type
     */
    const TYPE_TIME = 'time';

    /**
     * CTCP VERSION command event type
     */
    const TYPE_VERSION = 'version';

    /**
     * RAW message event type
     */
    const TYPE_RAW = 'raw';

    /**
     * Mapping of event types to their named parameters
     *
     * @var array
     */
    protected static $map = array(

        self::TYPE_QUIT => array(
            'message' => 0
        ),

        self::TYPE_JOIN => array(
            'channel' => 0
        ),

        self::TYPE_KICK => array(
            'channel' => 0,
            'user'    => 1,
            'comment' => 2
        ),

        self::TYPE_PART => array(
            'channel' => 0,
            'message' => 1
        ),

        self::TYPE_INVITE => array(
            'nickname' => 0,
            'channel'  => 1
        ),

        self::TYPE_MODE => array(
            'target'  => 0,
            'mode'    => 1,
            'limit'   => 2,
            'user'    => 3,
            'banmask' => 4
        ),

        self::TYPE_TOPIC => array(
            'channel' => 0,
            'topic'   => 1
        ),

        self::TYPE_PRIVMSG => array(
            'receiver' => 0,
            'text'     => 1
        ),

        self::TYPE_NOTICE => array(
            'nickname' => 0,
            'text'     => 1
        ),

        self::TYPE_ACTION => array(
            'target' => 0,
            'action' => 1
        ),

        self::TYPE_RAW => array(
            'message' => 0
        )

    );

    /**
     * Hostmask representing the originating user, if applicable
     *
     * @var Phergie_Hostmask
     */
    protected $hostmask;

    /**
     * Arguments included with the message
     *
     * @var array
     */
    protected $arguments;

    /**
     * Raw data sent by the server
     *
     * @var string
     */
    protected $rawData;

    /**
     * Sets the hostmask representing the originating user.
     *
     * @param Phergie_Hostmask $hostmask User hostmask
     *
     * @return Phergie_Event_Request Provides a fluent interface
     */
    public function setHostmask(Phergie_Hostmask $hostmask)
    {
        $this->hostmask = $hostmask;
        return $this;
    }

    /**
     * Returns the hostmask representing the originating user.
     *
     * @return Phergie_Event_Request|null Hostmask or NULL if none was set
     */
    public function getHostmask()
    {
        return $this->hostmask;
    }

    /**
     * Sets the arguments for the request.
     *
     * @param array $arguments Request arguments
     *
     * @return Phergie_Event_Request Provides a fluent interface
     */
    public function setArguments($arguments)
    {
        $this->arguments = $arguments;
        return $this;
    }

    /**
     * Sets the value of a single argument for the request.
     *
     * @param mixed  $argument Integer position (starting from 0) or the
     *        equivalent string name of the argument from self::$map
     * @param string $value    Value to assign to the argument
     *
     * @return Phergie_Event_Request Provides a fluent interface
     */
    public function setArgument($argument, $value)
    {
        $argument = $this->resolveArgument($argument);
        if ($argument !== null) {
            $this->arguments[$argument] = (string) $value;
        }
        return $this;
    }

    /**
     * Returns the arguments for the request.
     *
     * @return array
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    /**
     * Resolves an argument specification to an integer position.
     *
     * @param mixed $argument Integer position (starting from 0) or the
     *        equivalent string name of the argument from self::$map
     *
     * @return int|null Integer position of the argument or NULL if no
     *         corresponding argument was found
     */
    protected function resolveArgument($argument)
    {
        if (isset($this->arguments[$argument])) {
            return $argument;
        } else {
            $argument = strtolower($argument);
            if (isset(self::$map[$this->type][$argument])
                && isset($this->arguments[self::$map[$this->type][$argument]])
            ) {
                return self::$map[$this->type][$argument];
            }
        }
        return null;
    }

    /**
     * Returns a single specified argument for the request.
     *
     * @param mixed $argument Integer position (starting from 0) or the
     *        equivalent string name of the argument from self::$map
     *
     * @return string|null Argument value or NULL if none is set
     */
    public function getArgument($argument)
    {
        $argument = $this->resolveArgument($argument);
        if ($argument !== null) {
            return $this->arguments[$argument];
        }
        return null;
    }

    /**
     * Sets the raw buffer for the event.
     *
     * @param string $buffer Raw event buffer
     *
     * @return Phergie_Event_Request Provides a fluent interface
     */
    public function setRawData($buffer)
    {
        $this->rawData = $buffer;
        return $this;
    }

    /**
     * Returns the raw buffer sent from the server for the event.
     *
     * @return string
     */
    public function getRawData()
    {
        return $this->rawData;
    }

    /**
     * Returns the nick of the user who originated the event.
     *
     * @return string
     */
    public function getNick()
    {
        return $this->hostmask->getNick();
    }

    /**
     * Returns the channel name if the event occurred in a channel or the
     * user nick if the event was a private message directed at the bot by a
     * user.
     *
     * @return string
     */
    public function getSource()
    {
        if (substr($this->arguments[0], 0, 1) == '#') {
            return $this->arguments[0];
        }
        return $this->hostmask->getNick();
    }

    /**
     * Returns whether or not the event occurred within a channel.
     *
     * @return TRUE if the event is in a channel, FALSE otherwise
     */
    public function isInChannel()
    {
        return (substr($this->getSource(), 0, 1) == '#');
    }

    /**
     * Returns whether or not the event originated from a user.
     *
     * @return TRUE if the event is from a user, FALSE otherwise
     */
    public function isFromUser()
    {
        if (empty($this->hostmask)) {
            return false;
        }
        $username = $this->hostmask->getUsername();
        return !empty($username);
    }

    /**
     * Returns whether or not the event originated from the server.
     *
     * @return TRUE if the event is from the server, FALSE otherwise
     */
    public function isFromServer()
    {
        $username = $this->hostmask->getUsername();
        return empty($username);
    }

    /**
     * Provides access to named parameters via virtual "getter" methods.
     *
     * @param string $name      Name of the method called
     * @param array  $arguments Arguments passed to the method (should always
     *        be empty)
     *
     * @return mixed Method return value
     */
    public function __call($name, array $arguments)
    {
        if (!count($arguments) && substr($name, 0, 3) == 'get') {
            return $this->getArgument(substr($name, 3));
        }
    }

    /**
     * Checks to see if an event argument is assigned a value.
     *
     * @param string|int $offset Argument name or position beginning from 0
     *
     * @return bool TRUE if the argument has a value, FALSE otherwise
     * @see ArrayAccess::offsetExists()
     */
    public function offsetExists($offset)
    {
        return ($this->resolveArgument($offset) !== null);
    }

    /**
     * Returns the value of an event argument.
     *
     * @param string|int $offset Argument name or position beginning from 0
     *
     * @return string|null Argument value or NULL if none is set
     * @see ArrayAccess::offsetGet()
     */
    public function offsetGet($offset)
    {
        return $this->getArgument($offset);
    }

    /**
     * Sets the value of an event argument.
     *
     * @param string|int $offset Argument name or position beginning from 0
     * @param string     $value  New argument value
     *
     * @return void
     * @see ArrayAccess::offsetSet()
     */
    public function offsetSet($offset, $value)
    {
        $offset = $this->resolveArgument($offset);
        if ($offset !== null) {
            $this->arguments[$offset] = $value;
        }
    }

    /**
     * Removes the value set for an event argument.
     *
     * @param string|int $offset Argument name or position beginning from 0
     *
     * @return void
     * @see ArrayAccess::offsetUnset()
     */
    public function offsetUnset($offset)
    {
        if ($offset = $this->resolveArgument($offset)) {
            unset($this->arguments[$offset]);
        }
    }
}


code:
<?php
/**
 * Phergie
 *
 * PHP version 5
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.
 * It is also available through the world-wide-web at this URL:
 * http://phergie.org/license
 *
 * @category  Phergie
 * @package   Phergie
 * @author    Phergie Development Team <team@phergie.org>
 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
 * @license   http://phergie.org/license New BSD License
 * @link      http://pear.phergie.org/package/Phergie
 */

/**
 * Event originating from the server in response to an event sent by the
 * current client.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 * @link     http://www.irchelp.org/irchelp/rfc/chapter6.html
 */
class Phergie_Event_Response extends Phergie_Event_Abstract
{
    /**
     * <nickname> No such nick/channel
     *
     * Used to indicate the nickname parameter supplied to a command is currently
     * unused.
     */
    const ERR_NOSUCHNICK = '401';

    /**
     * <server name> No such server
     *
     * Used to indicate the server name given currently doesn't exist.
     */
    const ERR_NOSUCHSERVER = '402';

    /**
     * <channel name> No such channel
     *
     * Used to indicate the given channel name is invalid.
     */
    const ERR_NOSUCHCHANNEL = '403';

    /**
     * <channel name> Cannot send to channel
     *
     * Sent to a user who is either (a) not on a channel which is mode +n or (b) not
     * a chanop (or mode +v) on a channel which has mode +m set and is trying to send
     * a PRIVMSG message to that channel.
     */
    const ERR_CANNOTSENDTOCHAN = '404';

    /**
     * <channel name> You have joined too many channels
     *
     * Sent to a user when they have joined the maximum number of allowed channels
     * and they try to join another channel.
     */
    const ERR_TOOMANYCHANNELS = '405';

    /**
     * <nickname> There was no such nickname
     *
     * Returned by WHOWAS to indicate there is no history information for that
     * nickname.
     */
    const ERR_WASNOSUCHNICK = '406';

    /**
     * <target> Duplicate recipients. No message delivered
     *
     * Returned to a client which is attempting to send PRIVMSG/NOTICE using the
     * user@host destination format and for a user@host which has several
     * occurrences.
     */
    const ERR_TOOMANYTARGETS = '407';

    /**
     * No origin specified
     *
     * PING or PONG message missing the originator parameter which is required since
     * these commands must work without valid prefixes.
     */
    const ERR_NOORIGIN = '409';

    /**
     * No recipient given (<command>)
     */
    const ERR_NORECIPIENT = '411';

    /**
     * No text to send
     */
    const ERR_NOTEXTTOSEND = '412';

    /**
     * <mask> No toplevel domain specified
     */
    const ERR_NOTOPLEVEL = '413';

    /**
     * <mask> Wildcard in toplevel domain
     *
     * 412 - 414 are returned by PRIVMSG to indicate that the message wasn't
     * delivered for some reason. ERR_NOTOPLEVEL and ERR_WILDTOPLEVEL are errors that
     * are returned when an invalid use of "PRIVMSG $<server>" or "PRIVMSG #<host>"
     * is attempted.
     */
    const ERR_WILDTOPLEVEL = '414';

    /**
     * <command> Unknown command
     *
     * Returned to a registered client to indicate that the command sent is unknown
     * by the server.
     */
    const ERR_UNKNOWNCOMMAND = '421';

    /**
     * MOTD File is missing
     *
     * Server's MOTD file could not be opened by the server.
     */
    const ERR_NOMOTD = '422';

    /**
     * <server> No administrative info available
     *
     * Returned by a server in response to an ADMIN message when there is an error in
     * finding the appropriate information.
     */
    const ERR_NOADMININFO = '423';

    /**
     * File error doing <file op> on <file>
     *
     * Generic error message used to report a failed file operation during the
     * processing of a message.
     */
    const ERR_FILEERROR = '424';

    /**
     * No nickname given
     *
     * Returned when a nickname parameter expected for a command and isn't found.
     */
    const ERR_NONICKNAMEGIVEN = '431';

    /**
     * <nick> Erroneus nickname
     *
     * Returned after receiving a NICK message which contains characters which do not
     * fall in the defined set. See section x.x.x for details on valid nicknames.
     */
    const ERR_ERRONEUSNICKNAME = '432';

    /**
     * <nick> Nickname is already in use
     *
     * Returned when a NICK message is processed that results in an attempt to change
     * to a currently existing nickname.
     */
    const ERR_NICKNAMEINUSE = '433';

    /**
     * <nick> Nickname collision KILL
     *
     * Returned by a server to a client when it detects a nickname collision
     * (registered of a NICK that already exists by another server).
     */
    const ERR_NICKCOLLISION = '436';

    /**
     * <nick> <channel> They aren't on that channel
     *
     * Returned by the server to indicate that the target user of the command is not
     * on the given channel.
     */
    const ERR_USERNOTINCHANNEL = '441';

    /**
     * <channel> You're not on that channel
     *
     * Returned by the server whenever a client tries to perform a channel effecting
     * command for which the client isn't a member.
     */
    const ERR_NOTONCHANNEL = '442';

    /**
     * <user> <channel> is already on channel
     *
     * Returned when a client tries to invite a user to a channel they are already
     * on.
     */
    const ERR_USERONCHANNEL = '443';

    /**
     * <user> User not logged in
     *
     * Returned by the summon after a SUMMON command for a user was unable to be
     * performed since they were not logged in.
     */
    const ERR_NOLOGIN = '444';

    /**
     * SUMMON has been disabled
     *
     * Returned as a response to the SUMMON command. Must be returned by any server
     * which does not implement it.
     */
    const ERR_SUMMONDISABLED = '445';

    /**
     * USERS has been disabled
     *
     * Returned as a response to the USERS command. Must be returned by any server
     * which does not implement it.
     */
    const ERR_USERSDISABLED = '446';

    /**
     * You have not registered
     *
     * Returned by the server to indicate that the client must be registered before
     * the server will allow it to be parsed in detail.
     */
    const ERR_NOTREGISTERED = '451';

    /**
     * <command> Not enough parameters
     *
     * Returned by the server by numerous commands to indicate to the client that it
     * didn't supply enough parameters.
     */
    const ERR_NEEDMOREPARAMS = '461';

    /**
     * You may not reregister
     *
     * Returned by the server to any link which tries to change part of the
     * registered details (such as password or user details from second USER
     * message).
     */
    const ERR_ALREADYREGISTRED = '462';

    /**
     * Your host isn't among the privileged
     *
     * Returned to a client which attempts to register with a server which does not
     * been setup to allow connections from the host the attempted connection is
     * tried.
     */
    const ERR_NOPERMFORHOST = '463';

    /**
     * Password incorrect
     *
     * Returned to indicate a failed attempt at registering a connection for which a
     * password was required and was either not given or incorrect.
     */
    const ERR_PASSWDMISMATCH = '464';

    /**
     * You are banned from this server
     *
     * Returned after an attempt to connect and register yourself with a server which
     * has been setup to explicitly deny connections to you.
     */
    const ERR_YOUREBANNEDCREEP = '465';

    /**
     * <channel> Channel key already set
     */
    const ERR_KEYSET = '467';

    /**
     * <channel> Cannot join channel (+l)
     */
    const ERR_CHANNELISFULL = '471';

    /**
     * <char> is unknown mode char to me
     */
    const ERR_UNKNOWNMODE = '472';

    /**
     * <channel> Cannot join channel (+i)
     */
    const ERR_INVITEONLYCHAN = '473';

    /**
     * <channel> Cannot join channel (+b)
     */
    const ERR_BANNEDFROMCHAN = '474';

    /**
     * <channel> Cannot join channel (+k)
     */
    const ERR_BADCHANNELKEY = '475';

    /**
     * Permission Denied- You're not an IRC operator
     *
     * Any command requiring operator privileges to operate must return this error to
     * indicate the attempt was unsuccessful.
     */
    const ERR_NOPRIVILEGES = '481';

    /**
     * <channel> You're not channel operator
     *
     * Any command requiring 'chanop' privileges (such as MODE messages) must return
     * this error if the client making the attempt is not a chanop on the specified
     * channel.
     */
    const ERR_CHANOPRIVSNEEDED = '482';

    /**
     * You cant kill a server!
     *
     * Any attempts to use the KILL command on a server are to be refused and this
     * error returned directly to the client.
     */
    const ERR_CANTKILLSERVER = '483';

    /**
     * No O-lines for your host
     *
     * If a client sends an OPER message and the server has not been configured to
     * allow connections from the client's host as an operator, this error must be
     * returned.
     */
    const ERR_NOOPERHOST = '491';

    /**
     * Unknown MODE flag
     *
     * Returned by the server to indicate that a MODE message was sent with a
     * nickname parameter and that the a mode flag sent was not recognized.
     */
    const ERR_UMODEUNKNOWNFLAG = '501';

    /**
     * Cant change mode for other users
     *
     * Error sent to any user trying to view or change the user mode for a user other
     * than themselves.
     */
    const ERR_USERSDONTMATCH = '502';

    /**
     * Dummy reply number. Not used.
     */
    const RPL_NONE = '300';

    /**
     * [<reply>{<space><reply>}]
     *
     * Reply format used by USERHOST to list replies to the query list. The reply
     * string is composed as follows <reply> = <nick>['*'] '=' <'+'|'-'><hostname>
     * The '*' indicates whether the client has registered as an Operator. The '-' or
     * '+' characters represent whether the client has set an AWAY message or not
     * respectively.
     */
    const RPL_USERHOST = '302';

    /**
     * [<nick> {<space><nick>}]
     *
     * Reply format used by ISON to list replies to the query list.
     */
    const RPL_ISON = '303';

    /**
     * <nick> <away message>
     */
    const RPL_AWAY = '301';

    /**
     * You are no longer marked as being away
     */
    const RPL_UNAWAY = '305';

    /**
     * You have been marked as being away
     *
     * These replies are used with the AWAY command (if allowed). RPL_AWAY is sent to
     * any client sending a PRIVMSG to a client which is away. RPL_AWAY is only sent
     * by the server to which the client is connected. Replies RPL_UNAWAY and
     * RPL_NOWAWAY are sent when the client removes and sets an AWAY message.
     */
    const RPL_NOWAWAY = '306';

    /**
     * <nick> <user> <host> * <real name>
     */
    const RPL_WHOISUSER = '311';

    /**
     * <nick> <server> <server info>
     */
    const RPL_WHOISSERVER = '312';

    /**
     * <nick> is an IRC operator
     */
    const RPL_WHOISOPERATOR = '313';

    /**
     * <nick> <integer> seconds idle
     */
    const RPL_WHOISIDLE = '317';

    /**
     * <nick> End of /WHOIS list
     */
    const RPL_ENDOFWHOIS = '318';

    /**
     * <nick> {[@|+]<channel><space>}
     *
     * Replies 311 - 313, 317 - 319 are all replies generated in response to a WHOIS
     * message. Given that there are enough parameters present, the answering server
     * must either formulate a reply out of the above numerics (if the query nick is
     * found) or return an error reply. The '*' in RPL_WHOISUSER is there as the
     * literal character and not as a wild card. For each reply set, only
     * RPL_WHOISCHANNELS may appear more than once (for long lists of channel names).
     * The '@' and '+' characters next to the channel name indicate whether a client
     * is a channel operator or has been granted permission to speak on a moderated
     * channel. The RPL_ENDOFWHOIS reply is used to mark the end of processing a
     * WHOIS message.
     */
    const RPL_WHOISCHANNELS = '319';

    /**
     * <nick> <user> <host> * <real name>
     */
    const RPL_WHOWASUSER = '314';

    /**
     * <nick> End of WHOWAS
     *
     * When replying to a WHOWAS message, a server must use the replies
     * RPL_WHOWASUSER, RPL_WHOISSERVER or ERR_WASNOSUCHNICK for each nickname in the
     * presented list. At the end of all reply batches, there must be RPL_ENDOFWHOWAS
     * (even if there was only one reply and it was an error).
     */
    const RPL_ENDOFWHOWAS = '369';

    /**
     * Channel Users Name
     */
    const RPL_LISTSTART = '321';

    /**
     * <channel> <# visible> <topic>
     */
    const RPL_LIST = '322';

    /**
     * End of /LIST
     *
     * Replies RPL_LISTSTART, RPL_LIST, RPL_LISTEND mark the start, actual replies
     * with data and end of the server's response to a LIST command. If there are no
     * channels available to return, only the start and end reply must be sent.
     */
    const RPL_LISTEND = '323';

    /**
     * <channel> <mode> <mode params>
     */
    const RPL_CHANNELMODEIS = '324';

    /**
     * <channel> No topic is set
     */
    const RPL_NOTOPIC = '331';

    /**
     * <channel> <topic>
     *
     * When sending a TOPIC message to determine the channel topic, one of two
     * replies is sent. If the topic is set, RPL_TOPIC is sent back else RPL_NOTOPIC.
     */
    const RPL_TOPIC = '332';

    /**
     * <channel> <nick>
     *
     * Returned by the server to indicate that the attempted INVITE message was
     * successful and is being passed onto the end client.
     */
    const RPL_INVITING = '341';

    /**
     * <user> Summoning user to IRC
     *
     * Returned by a server answering a SUMMON message to indicate that it is
     * summoning that user.
     */
    const RPL_SUMMONING = '342';

    /**
     * <version>.<debuglevel> <server> <comments>
     *
     * Reply by the server showing its version details. The <version> is the version
     * of the software being used (including any patchlevel revisions) and the
     * <debuglevel> is used to indicate if the server is running in "debug mode". The
     * "comments" field may contain any comments about the version or further version
     * details.
     */
    const RPL_VERSION = '351';

    /**
     * <channel> <user> <host> <server> <nick> <H|G>[*][@|+] <hopcount> <real name>
     */
    const RPL_WHOREPLY = '352';

    /**
     * <name> End of /WHO list
     *
     * The RPL_WHOREPLY and RPL_ENDOFWHO pair are used to answer a WHO message. The
     * RPL_WHOREPLY is only sent if there is an appropriate match to the WHO query.
     * If there is a list of parameters supplied with a WHO message, a RPL_ENDOFWHO
     * must be sent after processing each list item with <name> being the item.
     */
    const RPL_ENDOFWHO = '315';

    /**
     * <channel> [[@|+]<nick> [[@|+]<nick> [...]]]
     */
    const RPL_NAMREPLY = '353';

    /**
     * <channel> End of /NAMES list
     *
     * To reply to a NAMES message, a reply pair consisting of RPL_NAMREPLY and
     * RPL_ENDOFNAMES is sent by the server back to the client. If there is no
     * channel found as in the query, then only RPL_ENDOFNAMES is returned. The
     * exception to this is when a NAMES message is sent with no parameters and all
     * visible channels and contents are sent back in a series of RPL_NAMEREPLY
     * messages with a RPL_ENDOFNAMES to mark the end.
     */
    const RPL_ENDOFNAMES = '366';

    /**
     * <mask> <server> <hopcount> <server info>
     */
    const RPL_LINKS = '364';

    /**
     * <mask> End of /LINKS list
     *
     * In replying to the LINKS message, a server must send replies back using the
     * RPL_LINKS numeric and mark the end of the list using an RPL_ENDOFLINKS reply.v
     */
    const RPL_ENDOFLINKS = '365';

    /**
     * <channel> <banid>
     */
    const RPL_BANLIST = '367';

    /**
     * <channel> End of channel ban list
     *
     * When listing the active 'bans' for a given channel, a server is required to
     * send the list back using the RPL_BANLIST and RPL_ENDOFBANLIST messages. A
     * separate RPL_BANLIST is sent for each active banid. After the banids have been
     * listed (or if none present) a RPL_ENDOFBANLIST must be sent.
     */
    const RPL_ENDOFBANLIST = '368';

    /**
     * <string>
     */
    const RPL_INFO = '371';

    /**
     * End of /INFO list
     *
     * A server responding to an INFO message is required to send all its 'info' in a
     * series of RPL_INFO messages with a RPL_ENDOFINFO reply to indicate the end of
     * the replies.
     */
    const RPL_ENDOFINFO = '374';

    /**
     * - <server> Message of the day -
     */
    const RPL_MOTDSTART = '375';

    /**
     * - <text>
     */
    const RPL_MOTD = '372';

    /**
     * End of /MOTD command
     *
     * When responding to the MOTD message and the MOTD file is found, the file is
     * displayed line by line, with each line no longer than 80 characters, using
     * RPL_MOTD format replies. These should be surrounded by a RPL_MOTDSTART (before
     * the RPL_MOTDs) and an RPL_ENDOFMOTD (after).
     */
    const RPL_ENDOFMOTD = '376';

    /**
     * You are now an IRC operator
     *
     * RPL_YOUREOPER is sent back to a client which has just successfully issued an
     * OPER message and gained operator status.
     */
    const RPL_YOUREOPER = '381';

    /**
     * <config file> Rehashing
     *
     * If the REHASH option is used and an operator sends a REHASH message, an
     * RPL_REHASHING is sent back to the operator.
     */
    const RPL_REHASHING = '382';

    /**
     * <server> <string showing server's local time>
     *
     * When replying to the TIME message, a server must send the reply using the
     * RPL_TIME format above. The string showing the time need only contain the
     * correct day and time there. There is no further requirement for the time
     * string.
     */
    const RPL_TIME = '391';

    /**
     * UserID Terminal Host
     */
    const RPL_USERSSTART = '392';

    /**
     * %-8s %-9s %-8s
     */
    const RPL_USERS = '393';

    /**
     * End of users
     */
    const RPL_ENDOFUSERS = '394';

    /**
     * Nobody logged in
     *
     * If the USERS message is handled by a server, the replies RPL_USERSTART,
     * RPL_USERS, RPL_ENDOFUSERS and RPL_NOUSERS are used. RPL_USERSSTART must be
     * sent first, following by either a sequence of RPL_USERS or a single
     * RPL_NOUSER. Following this is RPL_ENDOFUSERS.
     */
    const RPL_NOUSERS = '395';

    /**
     * Link <version & debug level> <destination> <next server>
     */
    const RPL_TRACELINK = '200';

    /**
     * Try. <class> <server>
     */
    const RPL_TRACECONNECTING = '201';

    /**
     * H.S. <class> <server>
     */
    const RPL_TRACEHANDSHAKE = '202';

    /**
     * ???? <class> [<client IP address in dot form>]
     */
    const RPL_TRACEUNKNOWN = '203';

    /**
     * Oper <class> <nick>
     */
    const RPL_TRACEOPERATOR = '204';

    /**
     * User <class> <nick>
     */
    const RPL_TRACEUSER = '205';

    /**
     * Serv <class> <int>S <int>C <server> <nick!user|*!*>@<host|server>
     */
    const RPL_TRACESERVER = '206';

    /**
     * <newtype> 0 <client name>
     */
    const RPL_TRACENEWTYPE = '208';

    /**
     * File <logfile> <debug level>
     *
     * The RPL_TRACE* are all returned by the server in response to the TRACE
     * message. How many are returned is dependent on the the TRACE message and
     * whether it was sent by an operator or not. There is no predefined order for
     * which occurs first. Replies RPL_TRACEUNKNOWN, RPL_TRACECONNECTING and
     * RPL_TRACEHANDSHAKE are all used for connections which have not been fully
     * established and are either unknown, still attempting to connect or in the
     * process of completing the 'server handshake'. RPL_TRACELINK is sent by any
     * server which handles a TRACE message and has to pass it on to another server.
     * The list of RPL_TRACELINKs sent in response to a TRACE command traversing the
     * IRC network should reflect the actual connectivity of the servers themselves
     * along that path. RPL_TRACENEWTYPE is to be used for any connection which does
     * not fit in the other categories but is being displayed anyway.
     */
    const RPL_TRACELOG = '261';

    /**
     * <linkname> <sendq> <sent messages> <sent bytes> <received messages> <received
     * bytes> <time open>
     */
    const RPL_STATSLINKINFO = '211';

    /**
     * <command> <count>
     */
    const RPL_STATSCOMMANDS = '212';

    /**
     * C <host> * <name> <port> <class>
     */
    const RPL_STATSCLINE = '213';

    /**
     * N <host> * <name> <port> <class>
     */
    const RPL_STATSNLINE = '214';

    /**
     * I <host> * <host> <port> <class>
     */
    const RPL_STATSILINE = '215';

    /**
     * K <host> * <username> <port> <class>
     */
    const RPL_STATSKLINE = '216';

    /**
     * Y <class> <ping frequency> <connect frequency> <max sendq>
     */
    const RPL_STATSYLINE = '218';

    /**
     * <stats letter> End of /STATS report
     */
    const RPL_ENDOFSTATS = '219';

    /**
     * L <hostmask> * <servername> <maxdepth>
     */
    const RPL_STATSLLINE = '241';

    /**
     * Server Up %d days %d%02d%02d
     */
    const RPL_STATSUPTIME = '242';

    /**
     * O <hostmask> * <name>
     */
    const RPL_STATSOLINE = '243';

    /**
     * H <hostmask> * <servername>
     */
    const RPL_STATSHLINE = '244';

    /**
     * <user mode string>
     *
     * To answer a query about a client's own mode, RPL_UMODEIS is sent back.
     */
    const RPL_UMODEIS = '221';

    /**
     * There are <integer> users and <integer> invisible on <integer> servers
     */
    const RPL_LUSERCLIENT = '251';

    /**
     * <integer> operator(s) online
     */
    const RPL_LUSEROP = '252';

    /**
     * <integer> unknown connection(s)
     */
    const RPL_LUSERUNKNOWN = '253';

    /**
     * <integer> channels formed
     */
    const RPL_LUSERCHANNELS = '254';

    /**
     * I have <integer> clients and <integer> servers
     *
     * In processing an LUSERS message, the server sends a set of replies from
     * RPL_LUSERCLIENT, RPL_LUSEROP, RPL_USERUNKNOWN, RPL_LUSERCHANNELS and
     * RPL_LUSERME. When replying, a server must send back RPL_LUSERCLIENT and
     * RPL_LUSERME. The other replies are only sent back if a non-zero count is found
     * for them.
     */
    const RPL_LUSERME = '255';

    /**
     * <server> Administrative info
     */
    const RPL_ADMINME = '256';

    /**
     * <admin info>
     */
    const RPL_ADMINLOC1 = '257';

    /**
     * <admin info>
     */
    const RPL_ADMINLOC2 = '258';

    /**
     * <admin info>
     *
     * When replying to an ADMIN message, a server is expected to use replies
     * RLP_ADMINME through to RPL_ADMINEMAIL and provide a text message with each.
     * For RPL_ADMINLOC1 a description of what city, state and country the server is
     * in is expected, followed by details of the university and department
     * (RPL_ADMINLOC2) and finally the administrative contact for the server (an
     * email address here is required) in RPL_ADMINEMAIL.
     */
    const RPL_ADMINEMAIL = '259';

    /**
     * Reply code sent by the server, which can be compared to the ERR_* and
     * RPL_* constants
     *
     * @var string
     */
    protected $code;

    /**
     * Reply code description sent by the server.
     *
     * @var string
     */
    protected $description;

    /**
     * Raw data sent by the server
     *
     * @var string
     */
    protected $rawData;

    /**
     * Event type
     *
     * @var string
     */
    protected $type = 'response';

    /**
     * Sets the reply code sent by the server.
     *
     * @param string $code Reply code
     *
     * @return Phergie_Event_Response Provides a fluent interface
     */
    public function setCode($code)
    {
        $this->code = $code;
        return $this;
    }

    /**
     * Returns the reply code sent by the server.
     *
     * @return string
     */
    public function getCode()
    {
        return $this->code;
    }

    /**
     * Sets the reply code description sent by the server.
     *
     * @param string $description Reply code description
     *
     * @return Phergie_Event_Response Provides a fluent interface
     */
    public function setDescription($description)
    {
        $this->description = $description;
        return $this;
    }

    /**
     * Returns the reply code description sent by the server.
     *
     * @return string
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Sets the raw buffer for the given event
     *
     * @param string $buffer Raw event buffer
     *
     * @return Phergie_Event_Response Provides a fluent interface
     */
    public function setRawData($buffer)
    {
        $this->rawData = $buffer;
        return $this;
    }

    /**
     * Returns the raw buffer that was sent from the server for that event
     *
     * @return string
     */
    public function getRawData()
    {
        return $this->rawData;
    }
}


code:
<?php
/**
 * Phergie
 *
 * PHP version 5
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.
 * It is also available through the world-wide-web at this URL:
 * http://phergie.org/license
 *
 * @category  Phergie
 * @package   Phergie
 * @author    Phergie Development Team <team@phergie.org>
 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
 * @license   http://phergie.org/license New BSD License
 * @link      http://pear.phergie.org/package/Phergie
 */

/**
 * Base class for all Phergie-related exceptions.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
class Phergie_Exception extends Exception
{
}