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 a hostmask.
 *
 * @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_Hostmask
{
    /**
     * Host
     *
     * @var string
     */
    protected $host;

    /**
     * Nick
     *
     * @var string
     */
    protected $nick;

    /**
     * Username
     *
     * @var string
     */
    protected $username;

    /**
     * Regular expression used to parse a hostmask
     *
     * @var string
     */
    protected static $regex = '/^([^!@]+)!(?:[ni]=)?([^@]+)@([^ ]+)/';

    /**
     * Constructor to initialize components of the hostmask.
     *
     * @param string $nick     Nick component
     * @param string $username Username component
     * @param string $host     Host component
     *
     * @return void
     */
    public function __construct($nick, $username, $host)
    {
        $this->nick = $nick;
        $this->username = $username;
        $this->host = $host;
    }

    /**
     * Returns whether a given string appears to be a valid hostmask.
     *
     * @param string $string Alleged hostmask string
     *
     * @return bool TRUE if the string appears to be a valid hostmask, FALSE
     *         otherwise
     */
    public static function isValid($string)
    {
        return (preg_match(self::$regex, $string) > 0);
    }

    /**
     * Parses a string containing the entire hostmask into a new instance of
     * this class.
     *
     * @param string $hostmask Entire hostmask including the nick, username,
     *        and host components
     *
     * @return Phergie_Hostmask New instance populated with data parsed from
     *         the provided hostmask string
     * @throws Phergie_Hostmask_Exception
     */
    public static function fromString($hostmask)
    {
        if (preg_match(self::$regex, $hostmask, $match)) {
            list(, $nick, $username, $host) = $match;
            return new self($nick, $username, $host);
        }

        throw new Phergie_Hostmask_Exception(
            'Invalid hostmask specified: "' . $hostmask . '"',
            Phergie_Hostmask_Exception::ERR_INVALID_HOSTMASK
        );
    }

    /**
     * Sets the hostname.
     *
     * @param string $host Hostname
     *
     * @return Phergie_Hostmask Provides a fluent interface
     */
    public function setHost($host)
    {
        $this->host = $host;

        return $this;
    }

    /**
     * Returns the hostname.
     *
     * @return string
     */
    public function getHost()
    {
        return $this->host;
    }

    /**
     * Sets the username of the user.
     *
     * @param string $username Username
     *
     * @return Phergie_Hostmask Provides a fluent interface
     */
    public function setUsername($username)
    {
        $this->username = $username;

        return $this;
    }

    /**
     * Returns the username of the user.
     *
     * @return string
     */
    public function getUsername()
    {
        return $this->username;
    }

    /**
     * Sets the nick of the user.
     *
     * @param string $nick User nick
     *
     * @return Phergie_Hostmask Provides a fluent interface
     */
    public function setNick($nick)
    {
        $this->nick = $nick;

        return $this;
    }

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

    /**
     * Returns the hostmask for the originating server or user.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->nick . '!' . $this->username . '@' . $this->host;
    }

    /**
     * Returns whether a given hostmask matches a given pattern.
     *
     * @param string $pattern  Pattern using conventions of a ban mask where
     *        represents a wildcard
     * @param string $hostmask Optional hostmask to match against, if not
     *        the current hostmask instance
     *
     * @return bool TRUE if the hostmask matches the pattern, FALSE otherwise
     * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_3 Examples
     */
    public function matches($pattern, $hostmask = null)
    {
        if (!$hostmask) {
            $hostmask = (string) $this;
        }

        $pattern = str_replace('*', '.*', $pattern);

        return (preg_match('#^' . $pattern . '$#', $hostmask) > 0);
    }
}


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 hostmask handling.
 *
 * @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_Hostmask_Exception extends Phergie_Exception
{
    /**
     * Error indicating that an invalid hostmask string was specified
     */
    const ERR_INVALID_HOSTMASK = 1;
}

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
 */

/**
 * Base class for plugins to provide event handler stubs and commonly needed
 * functionality.
 *
 * @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_Plugin_Abstract
{
    /**
     * Current configuration handler
     *
     * @var Phergie_Config
     */
    protected $config;

    /**
     * Plugin handler used to provide access to other plugins
     *
     * @var Phergie_Plugin_Handler
     */
    protected $plugins;

    /**
     * Current event handler instance for outgoing events
     *
     * @var Phergie_Event_Handler
     */
    protected $events;

    /**
     * Current connection instance
     *
     * @var Phergie_Connection
     */
    protected $connection;

    /**
     * Current incoming event being handled
     *
     * @var Phergie_Event_Request|Phergie_Event_Response
     */
    protected $event;

    /**
     * Plugin short name
     *
     * @var string
     */
    protected $name;

    /**
     * Returns the short name for the plugin based on its class name.
     *
     * @return string
     */
    public function getName()
    {
        if (empty($this->name)) {
            $this->name = substr(strrchr(get_class($this), '_'), 1);
        }
        return $this->name;
    }

    /**
     * Sets the short name for the plugin.
     *
     * @param string $name Plugin short name
     *
     * @return Phergie_Plugin_Abstract Provides a fluent interface
     */
    public function setName($name)
    {
        $this->name = (string) $name;
        return $this;
    }

    /**
     * Indicates that the plugin failed to load due to an unsatisfied
     * runtime requirement, such as a missing dependency.
     *
     * @param string $message Error message to provide more information
     *        about the reason for the failure
     *
     * @return Phergie_Plugin_Abstract Provides a fluent interface
     * @throws Phergie_Plugin_Exception Always
     */
    protected function fail($message)
    {
        throw new Phergie_Plugin_Exception(
            $message,
            Phergie_Plugin_Exception::ERR_REQUIREMENT_UNSATISFIED
        );
    }

    /**
     * Sets the current configuration handler.
     *
     * @param Phergie_Config $config Configuration handler
     *
     * @return Phergie_Plugin_Abstract Provides a fluent interface
     */
    public function setConfig(Phergie_Config $config)
    {
        $this->config = $config;
        return $this;
    }

    /**
     * Returns the current configuration handler or the value of a single
     * setting from it.
     *
     * @param string $name    Optional name of a setting for which the value
     *        should be returned instead of the entire configuration handler
     * @param mixed  $default Optional default value to return if no value
     *        is set for the setting indicated by $name
     *
     * @return Phergie_Config|mixed Configuration handler or value of the
     *         setting specified by $name
     * @throws Phergie_Plugin_Exception No configuration handler has been set
     */
    public function getConfig($name = null, $default = null)
    {
        if (empty($this->config)) {
            throw new Phergie_Plugin_Exception(
                'Configuration handler cannot be accessed before one is set',
                Phergie_Plugin_Exception::ERR_NO_CONFIG_HANDLER
            );
        }
        if (!is_null($name)) {
            if (!isset($this->config[$name])) {
                return $default;
            }
            return $this->config[$name];
        }
        return $this->config;
    }

    /**
     * Sets the current plugin handler.
     *
     * @param Phergie_Plugin_Handler $handler Plugin handler
     *
     * @return Phergie_Plugin_Abstract Provides a fluent interface
     */
    public function setPluginHandler(Phergie_Plugin_Handler $handler)
    {
        $this->plugins = $handler;
        return $this;
    }

    /**
     * Returns the current plugin handler.
     *
     * @return Phergie_Plugin_Handler
     * @throws Phergie_Plugin_Exception No plugin handler has been set
     */
    public function getPluginHandler()
    {
        if (empty($this->plugins)) {
            throw new Phergie_Plugin_Exception(
                'Plugin handler cannot be accessed before one is set',
                Phergie_Plugin_Exception::ERR_NO_PLUGIN_HANDLER
            );
        }
        return $this->plugins;
    }

    /**
     * Sets the current event handler.
     *
     * @param Phergie_Event_Handler $handler Event handler
     *
     * @return Phergie_Plugin_Abstract Provides a fluent interface
     */
    public function setEventHandler(Phergie_Event_Handler $handler)
    {
        $this->events = $handler;
        return $this;
    }

    /**
     * Returns the current event handler.
     *
     * @return Phergie_Event_Handler
     * @throws Phergie_Plugin_Exception No event handler has been set
     */
    public function getEventHandler()
    {
        if (empty($this->events)) {
            throw new Phergie_Plugin_Exception(
                'Event handler cannot be accessed before one is set',
                Phergie_Plugin_Exception::ERR_NO_EVENT_HANDLER
            );
        }
        return $this->events;
    }

    /**
     * Sets the current connection.
     *
     * @param Phergie_Connection $connection Connection
     *
     * @return Phergie_Plugin_Abstract Provides a fluent interface
     */
    public function setConnection(Phergie_Connection $connection)
    {
        $this->connection = $connection;
        return $this;
    }

    /**
     * Returns the current event connection.
     *
     * @return Phergie_Connection
     * @throws Phergie_Plugin_Exception No connection has been set
     */
    public function getConnection()
    {
        if (empty($this->connection)) {
            throw new Phergie_Plugin_Exception(
                'Connection cannot be accessed before one is set',
                Phergie_Plugin_Exception::ERR_NO_CONNECTION
            );
        }
        return $this->connection;
    }

    /**
     * Sets the current incoming event to be handled.
     *
     * @param Phergie_Event_Request|Phergie_Event_Response $event Event
     *
     * @return Phergie_Plugin_Abstract Provides a fluent interface
     */
    public function setEvent($event)
    {
        $this->event = $event;
        return $this;
    }

    /**
     * Returns the current incoming event to be handled.
     *
     * @return Phergie_Event_Request|Phergie_Event_Response
     */
    public function getEvent()
    {
        if (empty($this->event)) {
            throw new Phergie_Plugin_Exception(
                'Event cannot be accessed before one is set',
                Phergie_Plugin_Exception::ERR_NO_EVENT
            );
        }
        return $this->event;
    }

    /**
     * Provides do* methods with signatures identical to those of
     * Phergie_Driver_Abstract but that queue up events to be dispatched
     * later.
     *
     * @param string $name Name of the method called
     * @param array  $args Arguments passed in the call
     *
     * @return mixed
     */
    public function __call($name, array $args)
    {
        $subcmd = substr($name, 0, 2);
        if ($subcmd == 'do') {
            $type = strtolower(substr($name, 2));
            $this->getEventHandler()->addEvent($this, $type, $args);
        } else if ($subcmd != 'on') {
            throw new Phergie_Plugin_Exception(
                'Called invalid method ' . $name . ' in ' . get_class($this),
                Phergie_Plugin_Exception::ERR_INVALID_CALL
            );
        }
    }

    /**
     * Handler for when the plugin is initially loaded - useful for checking
     * runtime dependencies or performing any setup necessary for the plugin
     * to function properly such as initializing a database.
     *
     * @return void
     */
    public function onLoad()
    {
    }

    /**
     * Handler for when the bot initially connects to a server.
     *
     * @return void
     */
    public function onConnect()
    {
    }

    /**
     * Handler for each tick, a single iteration of the continuous loop
     * executed by the bot to receive, handle, and send events - useful for
     * repeated execution of tasks on a time interval.
     *
     * @return void
     */
    public function onTick()
    {
    }

    /**
     * Handler for when any event is received but has not yet been dispatched
     * to the plugin handler method specific to its event type.
     *
     * @return bool|null|void FALSE to short-circuit further event
     *         processing, TRUE or NULL otherwise
     */
    public function preEvent()
    {
    }

    /**
     * Handler for after plugin processing of an event has concluded but
     * before any events triggered in response by plugins are sent to the
     * server - useful for modifying outgoing events before they are sent.
     *
     * @return void
     */
    public function preDispatch()
    {
    }

    /**
     * Handler for after any events triggered by plugins in response to a
     * received event are sent to the server.
     *
     * @return void
     */
    public function postDispatch()
    {
    }

    /**
     * Handler for when the server prompts the client for a nick.
     *
     * @return void
     * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_1_2
     */
    public function onNick()
    {
    }

    /**
     * Handler for when a user obtains operator privileges.
     *
     * @return void
     * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_1_5
     */
    public function onOper()
    {
    }

    /**
     * Handler for when the client session is about to be terminated.
     *
     * @return void
     * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_1_6
     */
    public function onQuit()
    {
    }

    /**
     * Handler for when a user joins a channel.
     *
     * @return void
     * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_1
     */
    public function onJoin()
    {
    }

    /**
     * Handler for when a user leaves a channel.
     *
     * @return void
     * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_2
     */
    public function onPart()
    {
    }

    /**
     * Handler for when a user or channel mode is changed.
     *
     * @return void
     * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_3
     */
    public function onMode()
    {
    }

    /**
     * Handler for when a channel topic is viewed or changed.
     *
     * @return void
     * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_4
     */
    public function onTopic()
    {
    }

    /**
     * Handler for when a message is received from a channel or user.
     *
     * @return void
     * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_4_1
     */
    public function onPrivmsg()
    {
    }

    /**
     * Handler for when the bot receives a CTCP ACTION request.
     *
     * @return void
     * @link http://www.invlogic.com/irc/ctcp.html#4.4
     */
    public function onAction()
    {
    }

    /**
     * Handler for when a notice is received.
     *
     * @return void
     * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_4_2
     */
    public function onNotice()
    {
    }

    /**
     * Handler for when a user is kicked from a channel.
     *
     * @return void
     * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_8
     */
    public function onKick()
    {
    }

    /**
     * Handler for when the bot receives a ping event from a server, at
     * which point it is expected to respond with a pong request within
     * a short period else the server may terminate its connection.
     *
     * @return void
     * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_6_2
     */
    public function onPing()
    {
    }

    /**
     * Handler for when the bot receives a CTCP TIME request.
     *
     * @return void
     * @link http://www.invlogic.com/irc/ctcp.html#4.6
     */
    public function onTime()
    {
    }

    /**
     * Handler for when the bot receives a CTCP VERSION request.
     *
     * @return void
     * @link http://www.invlogic.com/irc/ctcp.html#4.1
     */
    public function onVersion()
    {
    }

    /**
     * Handler for when the bot receives a CTCP PING request.
     *
     * @return void
     * @link http://www.invlogic.com/irc/ctcp.html#4.2
     */
    public function onCtcpPing()
    {
    }

    /**
     * Handler for when the bot receives a CTCP request of an unknown type.
     *
     * @return void
     * @link http://www.invlogic.com/irc/ctcp.html
     */
    public function onCtcp()
    {
    }

    /**
     * Handler for when a reply is received for a CTCP PING request sent by
     * the bot.
     *
     * @return void
     * @link http://www.invlogic.com/irc/ctcp.html#4.2
     */
    public function onPingReply()
    {
    }

    /**
     * Handler for when a reply is received for a CTCP TIME request sent by
     * the bot.
     *
     * @return void
     * @link http://www.invlogic.com/irc/ctcp.html#4.6
     */
    public function onTimeReply()
    {
    }

    /**
     * Handler for when a reply is received for a CTCP VERSION request sent
     * by the bot.
     *
     * @return void
     * @link http://www.invlogic.com/irc/ctcp.html#4.1
     */
    public function onVersionReply()
    {
    }

    /**
     * Handler for when a reply received for a CTCP request of an unknown
     * type.
     *
     * @return void
     * @link http://www.invlogic.com/irc/ctcp.html
     */
    public function onCtcpReply()
    {
    }

    /**
     * Handler for when the bot receives a kill request from a server.
     *
     * @return void
     * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_6_1
     */
    public function onKill()
    {
    }

    /**
     * Handler for when the bot receives an invitation to join a channel.
     *
     * @return void
     * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_7
     */
    public function onInvite()
    {
    }

    /**
     * Handler for when a server response is received to a command issued by
     * the bot.
     *
     * @return void
     * @link http://irchelp.org/irchelp/rfc/chapter6.html
     */
    public function onResponse()
    {
    }
}


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_Plugin_Acl
 * @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_Plugin_Acl
 */

/**
 * Provides an access control system to limit reponses to events based on
 * the users who originate them.
 *
 * Configuration settings:
 * acl.whitelist - mapping of user hostmask patterns (optionally by host) to
 *                 plugins and methods where those plugins and methods will
 *                 only be accessible to those users (i.e. and inaccessible
 *                 to other users)
 * acl.blacklist - mapping of user hostmasks (optionally by host) to plugins
 *                 and methods where where those plugins and methods will be
 *                 inaccessible to those users but accessible to other users
 * acl.ops       - TRUE to automatically give access to whitelisted plugins
 *                 and methods to users with ops for events they initiate in
 *                 channels where they have ops
 *
 * The whitelist and blacklist settings are formatted like so:
 * <code>
 * 'acl.whitelist' => array(
 *     'hostname1' => array(
 *         'pattern1' => array(
 *             'plugins' => array(
 *                 'ShortPluginName'
 *             ),
 *             'methods' => array(
 *                 'methodName'
 *             )
 *         ),
 *     )
 * ),
 * </code>
 *
 * The hostname array dimension is optional; if not used, rules will be
 * applied across all connections. The pattern is a user hostmask pattern
 * where asterisks (*) are used for wildcards. Plugins and methods do not
 * need to be set to empty arrays if they are not used; simply exclude them.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Acl
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Acl
 * @uses     Phergie_Plugin_UserInfo pear.phergie.org
 */
class Phergie_Plugin_Acl extends Phergie_Plugin_Abstract
{
    /**
     * Checks for permission settings and removes the plugin if none are set.
     *
     * @return void
     */
    public function onLoad()
    {
        $this->plugins->getPlugin('UserInfo');

        if (!$this->getConfig('acl.blacklist')
            && !$this->getConfig('acl.whitelist')
        ) {
            $this->plugins->removePlugin($this);
        }
    }

    /**
     * Applies a set of rules to a plugin handler iterator.
     *
     * @param Phergie_Plugin_Iterator $iterator Iterator to receive rules
     * @param array                   $rules    Associate array containing
     *        either a 'plugins' key pointing to an array containing plugin
     *        short names to filter, a 'methods' key pointing to an array
     *        containing method names to filter, or both
     *
     * @return void
     */
    protected function applyRules(Phergie_Plugin_Iterator $iterator, array $rules)
    {
        if (!empty($rules['plugins'])) {
            $iterator->addPluginFilter($rules['plugins']);
        }
        if (!empty($rules['methods'])) {
            $iterator->addMethodFilter($rules['methods']);
        }
    }

    /**
     * Checks permission settings and short-circuits event processing for
     * blacklisted users.
     *
     * @return void
     */
    public function preEvent()
    {
        // Ignore server responses
        if ($this->event instanceof Phergie_Event_Response) {
            return;
        }

        // Ignore server-initiated events
        if (!$this->event->isFromUser()) {
            return;
        }

        // Get the iterator used to filter plugins when processing events
        $iterator = $this->plugins->getIterator();

        // Get configuration setting values
        $whitelist = $this->getConfig('acl.whitelist', array());
        $blacklist = $this->getConfig('acl.blacklist', array());
        $ops = $this->getConfig('acl.ops', false);

        // Support host-specific lists
        $host = $this->connection->getHost();
        foreach (array('whitelist', 'blacklist') as $var) {
            foreach ($$var as $pattern => $rules) {
                $regex = '/^' . str_replace('*', '.*', $pattern) . '$/i';
                if (preg_match($regex, $host)) {
                    ${$var} = ${$var}[$pattern];
                    break;
                }
            }
        }

        // Get information on the user initiating the current event
        $hostmask = $this->event->getHostmask();
        $isOp = $ops
              && $this->event->isInChannel()
              && $this->plugins->userInfo->isOp(
                $this->event->getNick(),
                $this->event->getSource()
              );

        // Filter whitelisted commands if the user is not on the whitelist
        if (!$isOp) {
            $whitelisted = false;
            foreach ($whitelist as $pattern => $rules) {
                if ($hostmask->matches($pattern)) {
                    $whitelisted = true;
                }
            }
            if (!$whitelisted) {
                foreach ($whitelist as $pattern => $rules) {
                    $this->applyRules($iterator, $rules);
                }
            }
        }

        // Filter blacklisted commands if the user is on the blacklist
        $blacklisted = false;
        foreach ($blacklist as $pattern => $rules) {
            if ($hostmask->matches($pattern)) {
                $this->applyRules($iterator, $rules);
                break;
            }
        }
    }

    /**
     * Clears filters on the plugin handler iterator.
     *
     * @return void
     */
    public function postDispatch()
    {
        $this->plugins->getIterator()->clearFilters();
    }
}


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_Plugin_AltNick
 * @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_Plugin_AltNick
 */

/**
 * Handles switching to alternate nicks in cases where the primary nick is
 * not available for use.
 *
 * @category Phergie
 * @package  Phergie_Plugin_AltNick
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_AltNick
 * @uses     extension spl
 */
class Phergie_Plugin_AltNick extends Phergie_Plugin_Abstract
{
    /**
     * Iterator for the alternate nick list
     *
     * @var ArrayIterator
     */
    protected $iterator;

    /**
     * Initializes instance variables.
     *
     * @return void
     */
    public function onConnect()
    {
        if ($this->config['altnick.nicks']) {
            if (is_string($this->config['altnick.nicks'])) {
                $this->config['altnick.nicks']
                    = array($this->config['altnick.nicks']);
            }
            $this->iterator = new ArrayIterator($this->config['altnick.nicks']);
        }
    }

    /**
     * Switches to alternate nicks as needed when nick collisions occur.
     *
     * @return void
     */
    public function onResponse()
    {
        // If no alternate nick list was found, return
        if (empty($this->iterator)) {
            return;
        }

        // If the response event indicates that the nick set is in use...
        $code = $this->getEvent()->getCode();
        if ($code == Phergie_Event_Response::ERR_NICKNAMEINUSE) {

            // Attempt to move to the next nick in the alternate nick list
            $this->iterator->next();

            // If another nick is available...
            if ($this->iterator->valid()) {

                // Switch to the new nick
                $altNick = $this->iterator->current();
                $this->doNick($altNick);

                // Update the connection to reflect the nick change
                $this->getConnection()->setNick($altNick);

            } else {
                // If no other nicks are available...

                // Terminate the connection
                $this->doQuit('All specified alternate nicks are in use');
            }
        }
    }
}


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_Plugin_AudioScrobbler
 * @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_Plugin_AudioScrobbler
 */

/**
 * Provides commands to look up information on tracks played by specific
 * users on the Last.fm and Libre.fm services.
 *
 * TODO: Make the "nick-binding" use an SQLite database instead of having them
 *       hard-coded in to the config file.
 *
 * Configuration settings:
 * "audioscrobbler.lastfm_api_key":  API given by last.fm (string).
 * "audioscrobbler.librefm_api_key": API key given by libre.fm (string).
 *
 * @category Phergie
 * @package  Phergie_Plugin_AudioScrobbler
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_AudioScrobbler
 * @uses     Phergie_Plugin_Command pear.phergie.org
 * @uses     Phergie_Plugin_Http pear.phergie.org
 * @uses     extension simplexml
 */
class Phergie_Plugin_AudioScrobbler extends Phergie_Plugin_Abstract
{
    /**
     * Last.FM API entry point
     *
     * @var string
     */
    protected $lastfmUrl = 'http://ws.audioscrobbler.com/2.0/';

    /**
     * Libre.FM API entry point
     *
     * @var string
     */
    protected $librefmUrl = 'http://alpha.dev.libre.fm/2.0/';

    /**
     * Scrobbler query string for user.getRecentTracks
     *
     * @var string
     */
    protected $query = '?method=user.getrecenttracks&user=%s&api_key=%s';

    /**
     * HTTP plugin
     *
     * @var Phergie_Plugin_Http
     */
    protected $http;

    /**
     * Check for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        if (!extension_loaded('simplexml')) {
            $this->fail('SimpleXML php extension is required');
        }

        $plugins = $this->getPluginHandler();
        $plugins->getPlugin('Command');
        $this->http = $plugins->getPlugin('Http');
    }

    /**
     * Command function to get a user's status on last.fm.
     *
     * @param string $user User identifier
     *
     * @return void
     */
    public function onCommandLastfm($user = null)
    {
        if ($key = $this->config['audioscrobbler.lastfm_api_key']) {
            $scrobbled = $this->getScrobbled($user, $this->lastfmUrl, $key);
            if ($scrobbled) {
                $this->doPrivmsg($this->getEvent()->getSource(), $scrobbled);
            }
        }
    }

    /**
     * Command function to get a user's status on libre.fm.
     *
     * @param string $user User identifier
     *
     * @return void
     */
    public function onCommandLibrefm($user = null)
    {
        if ($key = $this->config['audioscrobbler.librefm_api_key']) {
            $scrobbled = $this->getScrobbled($user, $this->librefmUrl, $key);
            if ($scrobbled) {
                $this->doPrivmsg($this->getEvent()->getSource(), $scrobbled);
            }
        }
    }

    /**
     * Simple Scrobbler API function to get a formatted string of the most
     * recent track played by a user.
     *
     * @param string $user Username to look up
     * @param string $url  Base URL of the scrobbler service
     * @param string $key  Scrobbler service API key
     *
     * @return string Formatted string of the most recent track played
     */
    public function getScrobbled($user, $url, $key)
    {
        $event = $this->getEvent();
        $user = $user ? $user : $event->getNick();
        $url = sprintf($url . $this->query, urlencode($user), urlencode($key));

        $response = $this->http->get($url);
        if ($response->isError()) {
            $this->doNotice(
                $event->getNick(),
                'Can\'t find status for ' . $user . ': HTTP ' .
                $response->getCode() . ' ' . $response->getMessage()
            );
            return false;
        }

        $xml = $response->getContent();
        if ($xml->error) {
            $this->doNotice(
                $event->getNick(),
                'Can\'t find status for ' . $user . ': API ' . $xml->error
            );
            return false;
        }

        $recenttracks = $xml->recenttracks;
        $track = $recenttracks->track[0];

        // If the user exists but has not scrobbled anything, the result will
        // be empty.
        if (empty($track->name) && empty($track->artist)) {
            $this->doNotice(
                $event->getNick(),
                'Can\'t find track information for ' . $recenttracks['user']
            );
            return false;
        }

        if (isset($track['nowplaying'])) {
            $msg = sprintf(
                '%s is listening to %s by %s',
                $recenttracks['user'],
                $track->name,
                $track->artist
            );
        } else {
            $msg = sprintf(
                '%s, %s was listening to %s by %s',
                date('j M Y, H:i', (int) $track->date['uts']),
                $recenttracks['user'],
                $track->name,
                $track->artist
            );
        }
        if ($track->streamable == 1) {
            $msg .= ' - ' . $track->url;
        }
        return $msg;
    }
}


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_Plugin_AutoJoin
 * @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_Plugin_AutoJoin
 */

/**
 * Automates the process of having the bot join one or more channels upon
 * connection to the server.
 *
 * The configuration setting autojoin.channels is used to determine which
 * channels to join. This setting can point to a comma-delimited string or
 * enumerated array containing a single list of channels or an associative
 * array keyed by hostname where each value is a comma-delimited string or
 * enumerated array containing a list of channels to join on the server
 * corresponding to that hostname.
 *
 * @category Phergie
 * @package  Phergie_Plugin_AutoJoin
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_AutoJoin
 */
class Phergie_Plugin_AutoJoin extends Phergie_Plugin_Abstract
{
    /**
     * Intercepts the end of the "message of the day" response and responds by
     * joining the channels specified in the configuration file.
     *
     * @return void
     */
    public function onResponse()
    {
        switch ($this->getEvent()->getCode()) {
        case Phergie_Event_Response::RPL_ENDOFMOTD:
        case Phergie_Event_Response::ERR_NOMOTD:
            if ($channels = $this->config['autojoin.channels']) {
                if (is_array($channels)) {
                    // Support autojoin.channels being in these formats:
                    // 'hostname' => array('#channel1', '#channel2', ... )
                    $host = $this->getConnection()->getHost();
                    if (isset($channels[$host])) {
                        $channels = $channels[$host];
                    }
                    if (is_array($channels)) {
                        $channels = implode(',', $channels);
                    }
                }
                $this->doJoin($channels);
            }
            $this->getPluginHandler()->removePlugin($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_Plugin_Beer
 * @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_Plugin_Beer
 */

/**
 * Processes requests to serve users beer.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Beer
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Beer
 * @uses     Phergie_Plugin_Command pear.phergie.org
 * @uses     Phergie_Plugin_Serve pear.phergie.org
 */
class Phergie_Plugin_Beer extends Phergie_Plugin_Abstract
{
    /**
     * Checks for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        $plugins = $this->plugins;
        $plugins->getPlugin('Command');
        $plugins->getPlugin('Serve');
    }

    /**
     * Processes requests to serve a user a beer.
     *
     * @param string $request Request including the target and an optional
     *        suggestion of what beer to serve
     *
     * @return void
     */
    public function onCommandBeer($request)
    {
        $format = $this->getConfig(
            'beer.format',
            'throws %target% %article% %item%.'
        );

        $this->plugins->getPlugin('Serve')->serve(
            dirname(__FILE__) . '/Beer/beer.db',
            'beer',
            $format,
            $request
        );
    }

    /**
     * Adds a "booze" alias for the "beer" command.
     *
     * @param string $request Request including the target and an optional
     *        suggestion of what beer to serve
     *
     * @return void
     */
    public function onCommandBooze($request)
    {
        $this->onCommandBeer($request);
    }
}


code:
<?php

if (!defined('__DIR__')) {
    define('__DIR__', dirname(__FILE__));
}

// Create database schema
echo 'Creating database', PHP_EOL;
$file = __DIR__ . '/beer.db';
if (file_exists($file)) {
    unlink($file);
}
$db = new PDO('sqlite:' . $file);
$db->exec('CREATE TABLE beer (name VARCHAR(255), link VARCHAR(255))');
$db->exec('CREATE UNIQUE INDEX beer_name ON beer (name)');
$insert = $db->prepare('INSERT INTO beer (name, link) VALUES (:name, :link)');

// Get raw beerme.com data set
echo 'Downloading beerme.com data set', PHP_EOL;
$file = __DIR__ . '/beerlist.txt';
if (!file_exists($file)) {
    copy('http://beerme.com/beerlist.php', $file);
}
$contents = file_get_contents($file);

// Extract data from data set
echo 'Processing beerme.com data', PHP_EOL;
$contents = tidy_repair_string($contents);
libxml_use_internal_errors(true);
$doc = new DOMDocument;
$doc->loadHTML($contents);
libxml_clear_errors();
$xpath = new DOMXPath($doc);
$beers = $xpath->query('//table[@class="beerlist"]/tr/td[1]');
$db->beginTransaction();
foreach ($beers as $beer) {
    $name = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $beer->textContent);
    $name = preg_replace('/\h*\v+\h*/', '', $name);
    $link = 'http://beerme.com' . $beer->childNodes->item(1)->getAttribute('href');
    $insert->execute(array($name, $link));
}
$db->commit();

// Clean up
echo 'Cleaning up', PHP_EOL;
unlink($file);

// Get and decompress openbeerdb.com data set
$archive = __DIR__ . '/beers.zip';
if (!file_exists($archive)) {
    echo 'Downloading openbeerdb.com data set', PHP_EOL;
    copy('http://openbeerdb.googlecode.com/files/beers.zip', $archive);
}

echo 'Decompressing openbeerdb.com data set', PHP_EOL;
$zip = new ZipArchive;
$zip->open($archive);
$zip->extractTo(__DIR__, 'beers/beers.csv');
$zip->close();
$file = __DIR__ . '/beers/beers.csv';

// Extract data from data set
echo 'Processing openbeerdb.com data', PHP_EOL;
$fp = fopen($file, 'r');
$columns = fgetcsv($fp, 0, '|');
$db->beginTransaction();
while ($line = fgetcsv($fp, 0, '|')) {
    $line = array_combine($columns, $line);
    $name = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $line['name']);
    $name = preg_replace('/\h*\v+\h*/', '', $name);
    $link = null;
    $insert->execute(array($name, $link));
}
$db->commit();
fclose($fp);

// Clean up
echo 'Cleaning up', PHP_EOL;
unlink($file);
unlink($archive);
rmdir(__DIR__ . '/beers');


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_Plugin_BeerScore
 * @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_Plugin_BeerScore
 */

/**
 * Handles incoming requests for beer scores.
 *
 * @category Phergie
 * @package  Phergie_Plugin_BeerScore
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_BeerScore
 * @uses     Phergie_Plugin_Http pear.phergie.org
 */
class Phergie_Plugin_BeerScore extends Phergie_Plugin_Abstract
{
    /**
     * Score result type
     *
     * @const string
     */
    const TYPE_SCORE = 'SCORE';

    /**
     * Search result type
     *
     * @const string
     */
    const TYPE_SEARCH = 'SEARCH';

    /**
     * Refine result type
     *
     * @const type
     */
    const TYPE_REFINE = 'REFINE';

    /**
     * Base API URL
     *
     * @const string
     */
    const API_BASE_URL = 'http://caedmon.net/beerscore/';

    /**
     * HTTP plugin
     *
     * @var Phergie_Plugin_Http
     */
    protected $http;

    /**
     * Checks for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        $this->http = $this->getPluginHandler()->getPlugin('Http');
    }

    /**
     * Handles beerscore commands.
     *
     * @param string $searchstring String to use in seaching for beer scores
     *
     * @return void
     */
    public function onCommandBeerscore($searchstring)
    {
        $event = $this->getEvent();
        $target = $event->getNick();
        $source = $event->getSource();

        $apiurl = self::API_BASE_URL . rawurlencode($searchstring);
        $response = $this->http->get($apiurl);

        if ($response->isError()) {
            $this->doNotice($target, 'Score not found (or failed to contact API)');
            return;
        }

        $result = $response->getContent();
        switch ($result->type) {
        case self::TYPE_SCORE:
            // small enough number to get scores
            foreach ($result->beer as $beer) {
                if ($beer->score === -1) {
                    $score = '(not rated)';
                } else {
                    $score = $beer->score;
                }
                $str
                    = "{$target}: rating for {$beer->name}" .
                    " = {$score} ({$beer->url})";
                $this->doPrivmsg($source, $str);
            }
            break;

        case self::TYPE_SEARCH:
            // only beer names, no scores
            $str = '';
            $found = 0;
            foreach ($result->beer as $beer) {
                if (isset($beer->score)) {
                    ++$found;
                    if ($beer->score === -1) {
                        $score = '(not rated)';
                    } else {
                        $score = $beer->score;
                    }
                    $str
                        = "{$target}: rating for {$beer->name}" .
                        " = {$score} ({$beer->url})";
                    $this->doPrivmsg($source, $str);
                } else {
                    $str .= "({$beer->name} -> {$beer->url}) ";
                }
            }
            $foundnum = $result->num - $found;
            $more = $found ? 'more ' : '';
            $str = "{$target}: {$foundnum} {$more}results... {$str}";
            $this->doPrivmsg($source, $str);
            break;

        case self::TYPE_REFINE:
            // Too many results; only output search URL
            if ($result->num < 100) {
                $num = $result->num;
            } else {
                $num = 'at least 100';
            }
            $resultsword = (($result->num > 1) ? 'results' : 'result');
            $str = "{$target}: {$num} {$resultsword}; {$result->searchurl}";
            $this->doPrivmsg($source, $str);
            break;
        }
    }
}


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_Plugin_Cache
 * @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_Plugin_Cache
 */

/**
 * Implements a generic cache to be used by other plugins.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Cache
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Cache
 */
class Phergie_Plugin_Cache extends Phergie_Plugin_Abstract
{
    /**
     * Key-value data storage for the cache
     *
     * @var array
     */
    protected $cache = array();

    /**
     * Stores a value in the cache.
     *
     * @param string   $key       Key to associate with the value
     * @param mixed    $data      Data to be stored
     * @param int|null $ttl       Time to live in seconds or NULL for forever
     * @param bool     $overwrite TRUE to overwrite any existing value
     *        associated with the specified key
     *
     * @return bool
     */
    public function store($key, $data, $ttl = 3600, $overwrite = true)
    {
        if (!$overwrite && isset($this->cache[$key])) {
            return false;
        }

        if ($ttl) {
            $expires = time()+$ttl;
        } else {
            $expires = null;
        }

        $this->cache[$key] = array('data' => $data, 'expires' => $expires);
        return true;

    }

    /**
     * Fetches a previously stored value.
     *
     * @param string $key Key associated with the value
     *
     * @return mixed Stored value or FALSE if no value or an expired value
     *         is associated with the specified key
     */
    public function fetch($key)
    {
        if (!isset($this->cache[$key])) {
            return false;
        }

        $item = $this->cache[$key];
        if (!is_null($item['expires']) && $item['expires'] < time()) {
            $this->expire($key);
            return false;
        }

        return $item['data'];
    }

    /**
     * Expires a value that has exceeded its time to live.
     *
     * @param string $key Key associated with the value to expire
     *
     * @return bool
     */
    protected function expire($key)
    {
        if (!isset($this->cache[$key])) {
            return false;
        }
        unset($this->cache[$key]);
        return true;
    }
}


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_Plugin_Caffeine
 * @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_Plugin_Caffeine
 */

/**
 * Processes requests to serve users caffeinated beverages.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Caffeine
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Caffeine
 * @uses     Phergie_Plugin_Command pear.phergie.org
 * @uses     Phergie_Plugin_Serve pear.phergie.org
 */
class Phergie_Plugin_Caffeine extends Phergie_Plugin_Abstract
{
    /**
     * Checks for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        $plugins = $this->plugins;
        $plugins->getPlugin('Command');
        $plugins->getPlugin('Serve');
    }

    /**
     * Processes requests to serve a user a caffeinated beverage.
     *
     * @param string $request Request including the target and an optional
     *        suggestion of what caffeinated beverage to serve
     *
     * @return void
     */
    public function onCommandCaffeine($request)
    {
        $format = $this->getConfig(
            'beer.format',
            'throws %target% %article% %item%.'
        );

        $this->plugins->getPlugin('Serve')->serve(
            dirname(__FILE__) . '/Caffeine/caffeine.db',
            'caffeine',
            $format,
            $request
        );
    }
}


code:
<?php

if (!defined('__DIR__')) {
    define('__DIR__', dirname(__FILE__));
}

// Create database schema
echo 'Creating database', PHP_EOL;
$file = __DIR__ . '/caffeine.db';
if (file_exists($file)) {
    unlink($file);
}
$db = new PDO('sqlite:' . $file);
$db->exec('CREATE TABLE caffeine (name VARCHAR(255), link VARCHAR(255))');
$db->exec('CREATE UNIQUE INDEX caffeine_name ON caffeine (name)');
$insert = $db->prepare('INSERT INTO caffeine (name, link) VALUES (:name, :link)');

// Get raw energyfiend.com data set
echo 'Downloading energyfiend.com data set', PHP_EOL;
$file = __DIR__ . '/the-caffeine-database.html';
if (!file_exists($file)) {
    copy('http://www.energyfiend.com/the-caffeine-database', $file);
}
$contents = file_get_contents($file);

// Extract data from data set
echo 'Processing energyfiend.com data', PHP_EOL;
$contents = tidy_repair_string($contents);
libxml_use_internal_errors(true);
$doc = new DOMDocument;
$doc->loadHTML($contents);
libxml_clear_errors();
$xpath = new DOMXPath($doc);
$caffeine = $xpath->query('//table[@id="caffeinedb"]//tr/td[1]');
$db->beginTransaction();
foreach ($caffeine as $drink) {
    $name = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $drink->textContent);
    $name = preg_replace('/\s*\v+\s*/', ' ', $name);
    if ($drink->firstChild->nodeName == 'a') {
        $link = 'http://energyfiend.com'
              . $drink->firstChild->getAttribute('href');
    } else {
        $link = null;
    }
    $insert->execute(array($name, $link));
}
$db->commit();

// Clean up
echo 'Cleaning up', PHP_EOL;
unlink($file);


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_Plugin_Censor
 * @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_Plugin_Censor
 */

/**
 * Facilitates censoring of event content or discardment of events
 * containing potentially offensive phrases depending on the value of the
 * configuration setting censor.mode ('off', 'censor', 'discard'). Also
 * provides access to a web service for detecting censored words so that
 * other plugins may optionally integrate and adjust behavior accordingly to
 * prevent discardment of events.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Censor
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Censor
 * @uses     extension soap
 */
class Phergie_Plugin_Censor extends Phergie_Plugin_Abstract
{
    /**
     * SOAP client to interact with the CDYNE Profanity Filter API
     *
     * @var SoapClient
     */
    protected $soap;

    /**
     * Checks for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        if (!extension_loaded('soap')) {
            $this->fail('The PHP soap extension is required');
        }

        if (!in_array($this->config['censor.mode'], array('censor', 'discard'))) {
            $this->plugins->removePlugin($this);
        }
    }

    /**
     * Returns a "clean" version of a given string.
     *
     * @param string $string String to clean
     *
     * @return string Cleaned string
     */
    public function cleanString($string)
    {
        if (empty($this->soap)) {
            $this->soap = new SoapClient('http://ws.cdyne.com/ProfanityWS/Profanity.asmx?wsdl');
        }
        $params = array('Text' => $string);
        $attempts = 0;
        while ($attempts < 3) {
            try {
                $response = $this->soap->SimpleProfanityFilter($params);
                break;
            } catch (SoapFault $e) {
                $attempts++;
                sleep(1);
            }
        }
        if ($attempts == 3) {
            return $string;
        }
        return $response->SimpleProfanityFilterResult->CleanText;
    }

    /**
     * Processes events before they are dispatched and either censors their
     * content or discards them if they contain potentially offensive
     * content.
     *
     * @return void
     */
    public function preDispatch()
    {
        $events = $this->events->getEvents();

        foreach ($events as $event) {
            switch ($event->getType()) {
                case Phergie_Event_Request::TYPE_PRIVMSG:
                case Phergie_Event_Request::TYPE_ACTION:
                case Phergie_Event_Request::TYPE_NOTICE:
                    $text = $event->getArgument(1);
                    $clean = $this->cleanString($text);
                    if ($text != $clean) {
                        if ($this->config['censor.mode'] == 'censor') {
                            $event->setArgument(1, $clean);
                        } else {
                            $this->events->removeEvent($event);
                        }
                    }
                    break;
            }
        }
    }
}


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_Plugin_Cocktail
 * @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_Plugin_Cocktail
 */

/**
 * Processes requests to serve users cocktail.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Cocktail
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Cocktail
 * @uses     Phergie_Plugin_Command pear.phergie.org
 * @uses     Phergie_Plugin_Serve pear.phergie.org
 */
class Phergie_Plugin_Cocktail extends Phergie_Plugin_Abstract
{
    /**
     * Checks for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        $plugins = $this->plugins;
        $plugins->getPlugin('Command');
        $plugins->getPlugin('Serve');
    }

    /**
     * Processes requests to serve a user a cocktail.
     *
     * @param string $request Request including the target and an optional
     *        suggestion of what cocktail to serve
     *
     * @return void
     */
    public function onCommandCocktail($request)
    {
        $format = $this->getConfig(
            'cocktail.format',
            'throws %target% %article% %item%.'
        );

        $this->plugins->getPlugin('Serve')->serve(
            dirname(__FILE__) . '/Cocktail/cocktail.db',
            'cocktail',
            $format,
            $request,
            true
        );
    }
}


code:
<?php

if (!defined('__DIR__')) {
    define('__DIR__', dirname(__FILE__));
}

// Create database schema
echo 'Creating database', PHP_EOL;
$file = __DIR__ . '/cocktail.db';
if (file_exists($file)) {
    unlink($file);
}
$db = new PDO('sqlite:' . $file);
$db->exec('CREATE TABLE cocktail (name VARCHAR(255), link VARCHAR(255))');
$db->exec('CREATE UNIQUE INDEX cocktail_name ON cocktail (name)');
$insert = $db->prepare('INSERT INTO cocktail (name, link) VALUES (:name, :link)');

// Get raw webtender.com data set
echo 'Downloading webtender.com data set', PHP_EOL;
$start = 1;
do {
    $file = __DIR__ . '/' . $start . '.html';
    if (file_exists($file)) {
        continue;
    }
    copy(
        'http://www.webtender.com/db/browse?level=2&dir=drinks&char=%2A&start=' . $start,
        $file
    );
    if (!isset($limit)) {
        $contents = file_get_contents($file);
        preg_match('/([0-9]+) found/', $contents, $match);
        $limit = $match[1] + (150 - ($match[1] % 150));
    }
    echo 'Got records ', $start, ' - ', min($start + 150, $limit), ' of ', $limit, PHP_EOL;
    $start += 150;
} while ($start < $limit);

// Extract data from data set
$start = 1;
while ($start < $limit) {
    echo 'Processing ', $start, ' - ', min($start + 150, $limit), ' of ', $limit, PHP_EOL;

    $file = __DIR__ . '/' . $start . '.html';
    $contents = file_get_contents($file);
    $contents = tidy_repair_string($contents);
    libxml_use_internal_errors(true);
    $doc = new DOMDocument;
    $doc->loadHTML($contents);
    libxml_clear_errors();
    $xpath = new DOMXPath($doc);

    $cocktails = $xpath->query('//li/a');
    $db->beginTransaction();
    foreach ($cocktails as $cocktail) {
        $name = $cocktail->nodeValue;
        $name = preg_replace('/ The$|^The |\s*\([^)]+\)\s*| #[0-9]+$/', '', $name);
        $name = html_entity_decode($name);
        $link = 'http://www.webtender.com' . $cocktail->getAttribute('href');
        $insert->execute(array($name, $link));
    }
    $db->commit();

    $start += 150;
}

// Clean up
echo 'Cleaning up', PHP_EOL;
$start = 1;
while ($start < $limit) {
    $file = __DIR__ . '/' . $start . '.html';
    unlink($file);
    $start += 150;
}


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_Plugin_Command
 * @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_Plugin_Command
 */

/**
 * Handles parsing and execution of commands sent by users via messages sent
 * to channels in which the bot is present or directly to the bot.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Command
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Command
 * @uses     extension reflection
 * @uses     Phergie_Plugin_Message pear.phergie.org
 */
class Phergie_Plugin_Command extends Phergie_Plugin_Abstract
{
    /**
     * Prefix for command method names
     *
     * @var string
     */
    const METHOD_PREFIX = 'onCommand';

    /**
     * Cache for command lookups used to confirm that methods exist and
     * parameter counts match
     *
     * @var array
     */
    protected $methods = array();

    /**
     * Load the Message plugin
     *
     * @return void
     */
    public function onLoad()
    {
        $plugins = $this->getPluginHandler();
        $plugins->getPlugin('Message');
    }

    /**
     * Populates the methods cache.
     *
     * @return void
     */
    public function populateMethodCache()
    {
        foreach ($this->getPluginHandler()->getPlugins() as $plugin) {
            $reflector = new ReflectionClass($plugin);
            foreach ($reflector->getMethods() as $method) {
                $name = $method->getName();
                if (strpos($name, self::METHOD_PREFIX) === 0
                    && !isset($this->methods[$name])
                ) {
                    $this->methods[$name] = array(
                        'total' => $method->getNumberOfParameters(),
                        'required' => $method->getNumberOfRequiredParameters()
                    );
                }
            }
        }
    }

    /**
     * Parses a given message and, if its format corresponds to that of a
     * defined command, calls the handler method for that command with any
     * provided parameters.
     *
     * @return void
     */
    public function onPrivmsg()
    {
        // Populate the methods cache if needed
        if (empty($this->methods)) {
            $this->populateMethodCache();
        }

        // Check for a prefixed message
        $msg = $this->plugins->message->getMessage();
        if ($msg === false) {
            return;
        }

        // Separate the command and arguments
        $parsed = preg_split('/\s+/', $msg, 2);
        $command = strtolower(array_shift($parsed));
        $args = count($parsed) ? array_shift($parsed) : '';

        // Resolve aliases to their corresponding commands
        $aliases = $this->getConfig('command.aliases', array());
        $result = preg_grep('/^' . preg_quote($command, '/') . '$/i', array_keys($aliases));
        if ($result) {
            $command = $aliases[array_shift($result)];
        }

        // Check to ensure the command exists
        $method = self::METHOD_PREFIX . ucfirst($command);
        if (empty($this->methods[$method])) {
            return;
        }

        // If no arguments are passed...
        if (empty($args)) {

            // If the method requires no arguments, call it
            if (empty($this->methods[$method]['required'])) {
                $this->getPluginHandler()->$method();
            }

        } else {
            // If arguments are passed...

            // Parse the arguments
            $args = preg_split('/\s+/', $args, $this->methods[$method]['total']);

            // If the minimum arguments are passed, call the method
            if ($this->methods[$method]['required'] <= count($args)) {
                call_user_func_array(
                    array($this->getPluginHandler(), $method),
                    $args
                );
            }
        }
    }
}


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_Plugin_Cookie
 * @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_Plugin_Cookie
 */

/**
 * Processes requests to serve users cookies.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Cookie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Cookie
 * @uses     Phergie_Plugin_Command pear.phergie.org
 * @uses     Phergie_Plugin_Serve pear.phergie.org
 */
class Phergie_Plugin_Cookie extends Phergie_Plugin_Abstract
{
    /**
     * Checks for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        $plugins = $this->plugins;
        $plugins->getPlugin('Command');
        $plugins->getPlugin('Serve');
    }

    /**
     * Processes requests to serve a user a cookie.
     *
     * @param string $request Request including the target and an optional
     *        suggestion of what cookie to serve
     *
     * @return void
     */
    public function onCommandCookie($request)
    {
        $format = $this->getConfig(
            'cookie.format',
            'throws %target% %article% %item%.'
        );

        $this->plugins->getPlugin('Serve')->serve(
            dirname(__FILE__) . '/Cookie/cookie.db',
            'cookies',
            $format,
            $request
        );
    }
}


code:
<?php

if (!defined('__DIR__')) {
    define('__DIR__', dirname(__FILE__));
}

// Create database schema
echo 'Creating database', PHP_EOL;
$file = __DIR__ . '/cookie.db';
if (file_exists($file)) {
    unlink($file);
}
$db = new PDO('sqlite:' . $file);
$db->exec('CREATE TABLE cookies (name VARCHAR(255), link VARCHAR(255))');
$db->exec('CREATE UNIQUE INDEX cookie_name ON cookies (name)');
$insert = $db->prepare('INSERT INTO cookies (name, link) VALUES (:name, :link)');

// Get Cookies list from http://en.wikipedia.org/wiki/List_of_cookies
echo 'Downloading data from Wikipedia', PHP_EOL;
$file = __DIR__ . '/cookieslist.txt';
if (!file_exists($file)) {
    copy('http://en.wikipedia.org/wiki/List_of_cookies', $file);
}
$contents = file_get_contents($file);

// Extract data from data set
echo 'Processing Wikipedia\'s cookies list', PHP_EOL;
$contents = tidy_repair_string($contents);
libxml_use_internal_errors(true);
$doc = new DOMDocument;
$doc->loadHTML($contents);
libxml_clear_errors();
$xpath = new DOMXPath($doc);

$cookies = $xpath->query('//table[@width="90%"]/tr/td[1]/a');

foreach ($cookies as $cookie) {
    $name = $cookie->textContent;
    $name = str_replace(
        array('(',')',"\n", 'cookies'),
        array('','', ' ', 'cookie'),
        $name
    );
    $name = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $name);
    $name = trim($name);
    $name = rtrim($name, 's');

    $link =  'http://en.wikipedia.org' . $cookie->getAttribute('href');
    $insert->execute(array($name, $link));
    echo 'added [' . $name . '] -> '. $link . PHP_EOL;
}

// Clean up
echo 'Cleaning up', PHP_EOL;
unlink($file);


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_Plugin_Cron
 * @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_Plugin_Cron
 */

/**
 * Allows callbacks to be registered for asynchronous execution.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Cron
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Cron
 */
class Phergie_Plugin_Cron extends Phergie_Plugin_Abstract
{
    /**
     * Array of all registered callbacks with delays and arguments
     *
     * @var array
     */
    protected $callbacks;

    /**
     * Returns a human-readable representation of a callback for debugging
     * purposes.
     *
     * @param callback $callback Callback to analyze
     *
     * @return string|boolean String representation of the callback or FALSE
     *         if the specified value is not a valid callback
     */
    protected function getCallbackString($callback)
    {
        if (!is_callable($callback)) {
            return false;
        }

        if (is_array($callback)) {
            $class = is_string($callback[0]) ?
                $callback[0] : get_class($callback[0]);
            $method = $class . '::' . $callback[1];
            return $method;
        }

        return $callback;
    }

    /**
     * Registers a callback for execution sometime after a given delay
     * relative to now.
     *
     * @param callback $callback  Callback to be registered
     * @param int      $delay     Delay in seconds from now when the callback
     *        will be executed
     * @param array    $arguments Arguments to pass to the callback when
     *        it's executed
     * @param bool     $repeat    TRUE to automatically re-register the
     *        callback for the same delay after it's executed, FALSE
     *        otherwise
     *
     * @return void
     */
    public function registerCallback($callback, $delay,
        array $arguments = array(), $repeat = false)
    {
        $callbackString = $this->getCallbackString($callback);
        if ($callbackString === false) {
            echo 'DEBUG(Cron): Invalid callback specified - ',
                var_export($callback, true), PHP_EOL;
            return;
        }

        $registered = time();
        $scheduled = $registered + $delay;

        $this->callbacks[] = array(
            'callback'   => $callback,
            'delay'      => $delay,
            'arguments'  => $arguments,
            'registered' => $registered,
            'scheduled'  => $scheduled,
            'repeat'     => $repeat,
        );

        echo 'DEBUG(Cron): Callback ', $callbackString,
            ' scheduled for ', date('H:i:s', $scheduled), PHP_EOL;
    }

    /**
     * Handles callback execution.
     *
     * @return void
     */
    public function onTick()
    {
        $time = time();
        foreach ($this->callbacks as $key => &$callback) {
            $callbackString = $this->getCallbackString($callback);

            $scheduled = $callback['scheduled'];
            if ($time < $scheduled) {
                continue;
            }

            if (empty($callback['arguments'])) {
                call_user_func($callback['callback']);
            } else {
                call_user_func_array(
                    $callback['callback'],
                    $callback['arguments']
                );
            }

            echo 'DEBUG(Cron): Callback ', $callbackString,
                ' scheduled for ', date('H:i:s', $scheduled), ',',
                ' executed at ', date('H:i:s', $now), PHP_EOL;

            if ($callback['repeat']) {
                $callback['scheduled'] = $time + $callback['delay'];
                echo 'DEBUG(Cron): Callback ', $callbackString,
                    ' scheduled for ', date('H:i:s', $callback['scheduled']),
                    PHP_EOL;
            } else {
                echo 'DEBUG(Cron): Callback ', $callbackString,
                    ' removed from callback list', PHP_EOL;
                unset($this->callbacks[$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_Plugin_Ctcp
 * @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_Plugin_Ctcp
 */

/**
 * Responds to various CTCP requests sent by the server and users.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Ctcp
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Ctcp
 * @link     http://www.irchelp.org/irchelp/rfc/ctcpspec.html
 */
class Phergie_Plugin_Ctcp extends Phergie_Plugin_Abstract
{
    /**
     * Responds to a CTCP TIME request from a user with the current local
     * time.
     *
     * @return void
     */
    public function onTime()
    {
        $source = $this->getEvent()->getSource();
        $this->doTime($source, strftime('%c %z'));
    }

    /**
     * Responds to a CTCP VERSION request from a user with the codebase
     * version.
     *
     * @return void
     */
    public function onVersion()
    {
        $source = $this->getEvent()->getSource();
        $msg = 'Phergie ' . Phergie_Bot::VERSION . ' (http://phergie.org)';
        $this->doVersion($source, $msg);
    }

    /**
     * Responds to a CTCP PING request from a user.
     *
     * @return void
     */
    public function onCtcpPing()
    {
        $event = $this->getEvent();
        $source = $event->getSource();
        $handshake = $event->getArgument(1);
        $this->doPing($source, $handshake);
    }

    /**
     * Responds to a CTCP FINGER request from a user.
     *
     * @return void
     */
    public function onFinger()
    {
        $connection = $this->getConnection();
        $name = $connection->getNick();
        $realname = $connection->getRealname();
        $username = $connection->getUsername();

        $finger
            = (empty($realname) ? $realname : $name) .
            ' (' . (!empty($username) ? $username : $name) . ')';

        $this->doFinger($source, $finger);
    }
}
?>


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_Plugin_Daddy
 * @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_Plugin_Daddy
 */

/**
 * Simply responds to messages addressed to the bot that contain the phrase
 * "Who's your daddy?" and related variations.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Daddy
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Daddy
 */
class Phergie_Plugin_Daddy extends Phergie_Plugin_Abstract
{
    /**
     * Checks messages for the question to which it should respond and sends a
     * response when appropriate
     *
     * @return void
     */
    public function onPrivmsg()
    {
        $config = $this->getConfig();
        $prefix = $config['command.prefix'];
        $event = $this->getEvent();
        $text = $event->getArgument(1);
        $target = $event->getNick();
        $source = $event->getSource();
        $pattern
            = '/' . preg_quote($prefix) .
            '\s*?who\'?s y(?:our|a) ([^?]+)\??/iAD';
        if (preg_match($pattern, $text, $m)) {
            $msg = 'You\'re my ' . $m[1] . ', ' . $target . '!';
            $this->doPrivmsg($source, $msg);
        }
    }
}


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_Plugin_Encoding
 * @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_Plugin_Encoding
 */

/**
 * Handles decoding markup entities and converting text between character
 * encodings.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Encoding
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Encoding
 */
class Phergie_Plugin_Encoding extends Phergie_Plugin_Abstract
{
    /**
     * Lookup table for entity conversions not supported by
     * html_entity_decode()
     *
     * @var array
     * @link http://us.php.net/manual/en/function.get-html-translation-table.php#73409
     * @link http://us.php.net/manual/en/function.get-html-translation-table.php#73410
     */
    protected static $entities = array(
        'α' => 913,
        ''' => 39,
        'β' => 914,
        '•' => 149,
        'χ' => 935,
        'ˆ' => 94,
        'δ' => 916,
        'ε' => 917,
        'η' => 919,
        'ƒ' => 402,
        'γ' => 915,
        'ι' => 921,
        'κ' => 922,
        'λ' => 923,
        '“' => 147,
        '‹' => 139,
        '‘' => 145,
        '—' => 151,
        '−' => 45,
        'μ' => 924,
        '–' => 150,
        'ν' => 925,
        'œ' => 140,
        'ω' => 937,
        'ο' => 927,
        'φ' => 934,
        'π' => 928,
        'ϖ' => 982,
        'ψ' => 936,
        '”' => 148,
        'ρ' => 929,
        '›' => 155,
        '’' => 146,
        'š' => 138,
        'σ' => 931,
        'ς' => 962,
        'τ' => 932,
        'θ' => 920,
        'ϑ' => 977,
        '˜' => 126,
        '™' => 153,
        'ϒ' => 978,
        'υ' => 933,
        'ξ' => 926,
        'ÿ' => 159,
        'ζ' => 918,
    );

    /**
     * Decodes markup entities in a given string.
     *
     * @param string $string  String containing markup entities
     * @param string $charset Optional character set name to use in decoding
     *        entities, defaults to UTF-8
     *
     * @return string String with markup entities decoded
     */
    public function decodeEntities($string, $charset = 'UTF-8')
    {
        $string = str_ireplace(
            array_keys(self::$entities),
            array_map('chr', self::$entities),
            $string
        );
        $string = html_entity_decode($string, ENT_QUOTES, $charset);
        $string = preg_replace(
            array('/&#0*([0-9]+);/me', '/&#x0*([a-f0-9]+);/mei'),
            array('$this->codeToUtf(\\1)', '$this->codeToUtf(hexdec(\\1))'),
            $string
        );
        return $string;
    }

    /**
     * Converts a given unicode to its UTF-8 equivalent.
     *
     * @param int $code Code to convert
     * @return string Character corresponding to code
     */
    public function codeToUtf8($code)
    {
        $code = (int) $code;
        switch ($code) {
            // 1 byte, 7 bits
            case 0:
                return chr(0);
            case ($code & 0x7F):
                return chr($code);

            // 2 bytes, 11 bits
            case ($code & 0x7FF):
                return chr(0xC0 | (($code >> 6) & 0x1F)) .
                       chr(0x80 | ($code & 0x3F));

            // 3 bytes, 16 bits
            case ($code & 0xFFFF):
                return chr(0xE0 | (($code >> 12) & 0x0F)) .
                       chr(0x80 | (($code >> 6) & 0x3F)) .
                       chr(0x80 | ($code & 0x3F));

            // 4 bytes, 21 bits
            case ($code & 0x1FFFFF):
                return chr(0xF0 | ($code >> 18)) .
                       chr(0x80 | (($code >> 12) & 0x3F)) .
                       chr(0x80 | (($code >> 6) & 0x3F)) .
                       chr(0x80 | ($code & 0x3F));
        }
    }

    /**
     * Transliterates characters in a given string where possible.
     *
     * @param string $string      String containing characters to
     *        transliterate
     * @param string $charsetFrom Optional character set of the string,
     *        defaults to UTF-8
     * @param string $charsetTo   Optional character set to which the string
     *        should be converted, defaults to ISO-8859-1
     *
     * @return string String with characters transliterated or the original
     *         string if transliteration was not possible
     */
    public function transliterate($string, $charsetFrom = 'UTF-8', $charsetTo = 'ISO-8859-1')
    {
        // @link http://pecl.php.net/package/translit
        if (function_exists('transliterate')) {
            $string = transliterate($string, array('han_transliterate', 'diacritical_remove'), $charsetFrom, $charsetTo);
        } elseif (function_exists('iconv')) {
            $string = iconv($charsetFrom, $charsetTo . '//TRANSLIT', $string);
        } else {
            // @link http://stackoverflow.com/questions/1284535/php-transliteration/1285491#1285491
            $string = preg_replace(
                '~&([a-z]{1,2})(acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml);~i',
                '$1',
                htmlentities($string, ENT_COMPAT, $charsetFrom)
            );
        }
        return $string;
    }
}


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 plugin handling.
 *
 * @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_Plugin_Exception extends Phergie_Exception
{
    /**
     * Error indicating that a path containing plugins was specified, but
     * did not reference a readable directory
     */
    const ERR_DIRECTORY_NOT_READABLE = 1;

    /**
     * Error indicating that an attempt was made to locate the class for a
     * specified plugin, but the class could not be found
     */
    const ERR_CLASS_NOT_FOUND = 2;

    /**
     * Error indicating that an attempt was made to locate the class for a
     * specified plugin, but that the found class did not extend the base
     * plugin class
     */
    const ERR_INCORRECT_BASE_CLASS = 3;

    /**
     * Error indicating that an attempt was made to locate the class for a
     * specified plugin, but that the found class cannot be instantiated
     */
    const ERR_CLASS_NOT_INSTANTIABLE = 4;

    /**
     * Error indicating that an attempt was made to access a plugin that had
     * not been loaded and autoloading was not enabled to load it
     */
    const ERR_PLUGIN_NOT_LOADED = 5;

    /**
     * Error indicating that an attempt was made to access the configuration
     * handler before one had been set
     */
    const ERR_NO_CONFIG_HANDLER = 6;

    /**
     * Error indicating that an attempt was made to access the plugin
     * handler before one had been set
     */
    const ERR_NO_PLUGIN_HANDLER = 7;

    /**
     * Error indicating that an attempt was made to access the event
     * handler before one had been set
     */
    const ERR_NO_EVENT_HANDLER = 8;

    /**
     * Error indicating that an attempt was made to access the connection
     * before one had been set
     */
    const ERR_NO_CONNECTION = 9;

    /**
     * Error indicating that an attempt was made to access the current
     * incoming event before one had been set
     */
    const ERR_NO_EVENT = 10;

    /**
     * Error indicating that a dependency of the plugin was unavailable at
     * the time that an attempt was made to load it
     */
    const ERR_REQUIREMENT_UNSATISFIED = 11;

    /**
     * Error indicating that a call was made to a nonexistent plugin method
     * and that its __call() implementation did not process that call as an
     * attempt to trigger an event - this is intended to aid in debugging of
     * such situations
     */
    const ERR_INVALID_CALL = 12;

    /**
     * Error indicating that a fatal runtime issue was encountered within a
     * plugin
     */
    const ERR_FATAL_ERROR = 13;

    /**
     * Error indicating that a class specified to be used for iterating
     * plugins cannot be found by the autoloader or does not extend
     * FilterIterator
     */
    const ERR_INVALID_ITERATOR_CLASS = 14;
}