--- /dev/null
+Settings.php
+*.db
--- /dev/null
+Copyright (c) 2010, Phergie Development Team
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+Neither the name of the Phergie Development Team nor the names of its
+contributors may be used to endorse or promote products derived from this
+software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null
+<?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
+ */
+
+/**
+ * Autoloader for Phergie classes.
+ *
+ * @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_Autoload
+{
+ /**
+ * Constructor to add the base Phergie path to the include_path.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ $path = dirname(__FILE__);
+ $includePath = get_include_path();
+ $includePathList = explode(PATH_SEPARATOR, $includePath);
+ if (!in_array($path, $includePathList)) {
+ self::addPath($path);
+ }
+ }
+
+ /**
+ * Autoload callback for loading class files.
+ *
+ * @param string $class Class to load
+ *
+ * @return void
+ */
+ public function load($class)
+ {
+ if (substr($class, 0, 8) == 'Phergie_') {
+ $class = substr($class, 8);
+ }
+ include str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php';
+ }
+
+ /**
+ * Registers an instance of this class as an autoloader.
+ *
+ * @return void
+ */
+ public static function registerAutoloader()
+ {
+ spl_autoload_register(array(new self, 'load'));
+ }
+
+ /**
+ * Add a path to the include path.
+ *
+ * @param string $path Path to add
+ *
+ * @return void
+ */
+ public static function addPath($path)
+ {
+ set_include_path($path . PATH_SEPARATOR . get_include_path());
+ }
+}
--- /dev/null
+<?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
+ */
+
+/**
+ * Composite class for other components to represent 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_Bot
+{
+ /**
+ * Current version of Phergie
+ */
+ const VERSION = '2.0.1';
+
+ /**
+ * Current driver instance
+ *
+ * @var Phergie_Driver_Abstract
+ */
+ protected $driver;
+
+ /**
+ * Current configuration instance
+ *
+ * @var Phergie_Config
+ */
+ protected $config;
+
+ /**
+ * Current connection handler instance
+ *
+ * @var Phergie_Connection_Handler
+ */
+ protected $connections;
+
+ /**
+ * Current plugin handler instance
+ *
+ * @var Phergie_Plugin_Handler
+ */
+ protected $plugins;
+
+ /**
+ * Current event handler instance
+ *
+ * @var Phergie_Event_Handler
+ */
+ protected $events;
+
+ /**
+ * Current end-user interface instance
+ *
+ * @var Phergie_Ui_Abstract
+ */
+ protected $ui;
+
+ /**
+ * Current processor instance
+ *
+ * @var Phergie_Process_Abstract
+ */
+ protected $processor;
+
+ /**
+ * Returns a driver instance, creating one of the default class if
+ * none has been set.
+ *
+ * @return Phergie_Driver_Abstract
+ */
+ public function getDriver()
+ {
+ if (empty($this->driver)) {
+ // Check if a driver has been defined in the configuration to use
+ // as the default
+ $config = $this->getConfig();
+ if (isset($config['driver'])) {
+ $class = 'Phergie_Driver_' . ucfirst($config['driver']);
+ } else {
+ // Otherwise default to the Streams driver.
+ $class = 'Phergie_Driver_Streams';
+ }
+
+ $this->driver = new $class;
+ }
+ return $this->driver;
+ }
+
+ /**
+ * Sets the driver instance to use.
+ *
+ * @param Phergie_Driver_Abstract $driver Driver instance
+ *
+ * @return Phergie_Bot Provides a fluent interface
+ */
+ public function setDriver(Phergie_Driver_Abstract $driver)
+ {
+ $this->driver = $driver;
+ return $this;
+ }
+
+ /**
+ * Sets the configuration to use.
+ *
+ * @param Phergie_Config $config Configuration instance
+ *
+ * @return Phergie_Runner_Abstract Provides a fluent interface
+ */
+ public function setConfig(Phergie_Config $config)
+ {
+ $this->config = $config;
+ return $this;
+ }
+
+ /**
+ * Returns the entire configuration in use or the value of a specific
+ * configuration setting.
+ *
+ * @param string $index Optional index of a specific configuration
+ * setting for which the corresponding value should be returned
+ * @param mixed $default Value to return if no match is found for $index
+ *
+ * @return mixed Value corresponding to $index or the entire
+ * configuration if $index is not specified
+ */
+ public function getConfig($index = null, $default = null)
+ {
+ if (empty($this->config)) {
+ $this->config = new Phergie_Config;
+ $this->config->read('Settings.php');
+ }
+ if ($index !== null) {
+ if (isset($this->config[$index])) {
+ return $this->config[$index];
+ } else {
+ return $default;
+ }
+ }
+ return $this->config;
+ }
+
+ /**
+ * Returns a plugin handler instance, creating it if it does not already
+ * exist and using a default class if none has been set.
+ *
+ * @return Phergie_Plugin_Handler
+ */
+ public function getPluginHandler()
+ {
+ if (empty($this->plugins)) {
+ $this->plugins = new Phergie_Plugin_Handler(
+ $this->getConfig(),
+ $this->getEventHandler()
+ );
+ }
+ return $this->plugins;
+ }
+
+ /**
+ * Sets the plugin handler instance to use.
+ *
+ * @param Phergie_Plugin_Handler $handler Plugin handler instance
+ *
+ * @return Phergie_Bot Provides a fluent interface
+ */
+ public function setPluginHandler(Phergie_Plugin_Handler $handler)
+ {
+ $this->plugins = $handler;
+ return $this;
+ }
+
+ /**
+ * Returns an event handler instance, creating it if it does not already
+ * exist and using a default class if none has been set.
+ *
+ * @return Phergie_Event_Handler
+ */
+ public function getEventHandler()
+ {
+ if (empty($this->events)) {
+ $this->events = new Phergie_Event_Handler;
+ }
+ return $this->events;
+ }
+
+ /**
+ * Sets the event handler instance to use.
+ *
+ * @param Phergie_Event_Handler $handler Event handler instance
+ *
+ * @return Phergie_Bot Provides a fluent interface
+ */
+ public function setEventHandler(Phergie_Event_Handler $handler)
+ {
+ $this->events = $handler;
+ return $this;
+ }
+
+ /**
+ * Returns a connection handler instance, creating it if it does not
+ * already exist and using a default class if none has been set.
+ *
+ * @return Phergie_Connection_Handler
+ */
+ public function getConnectionHandler()
+ {
+ if (empty($this->connections)) {
+ $this->connections = new Phergie_Connection_Handler;
+ }
+ return $this->connections;
+ }
+
+ /**
+ * Sets the connection handler instance to use.
+ *
+ * @param Phergie_Connection_Handler $handler Connection handler instance
+ *
+ * @return Phergie_Bot Provides a fluent interface
+ */
+ public function setConnectionHandler(Phergie_Connection_Handler $handler)
+ {
+ $this->connections = $handler;
+ return $this;
+ }
+
+ /**
+ * Returns an end-user interface instance, creating it if it does not
+ * already exist and using a default class if none has been set.
+ *
+ * @return Phergie_Ui_Abstract
+ */
+ public function getUi()
+ {
+ if (empty($this->ui)) {
+ $this->ui = new Phergie_Ui_Console;
+ }
+ return $this->ui;
+ }
+
+ /**
+ * Sets the end-user interface instance to use.
+ *
+ * @param Phergie_Ui_Abstract $ui End-user interface instance
+ *
+ * @return Phergie_Bot Provides a fluent interface
+ */
+ public function setUi(Phergie_Ui_Abstract $ui)
+ {
+ $this->ui = $ui;
+ return $this;
+ }
+
+ /**
+ * Returns a processer instance, creating one if none exists.
+ *
+ * @return Phergie_Process_Abstract
+ */
+ public function getProcessor()
+ {
+ if (empty($this->processor)) {
+ $class = 'Phergie_Process_Standard';
+
+ $type = $this->getConfig('processor');
+ if (!empty($type)) {
+ $class = 'Phergie_Process_' . ucfirst($type);
+ }
+
+ $this->processor = new $class(
+ $this,
+ $this->getConfig('processor.options', array())
+ );
+ }
+ return $this->processor;
+ }
+
+ /**
+ * Sets the processer instance to use.
+ *
+ * @param Phergie_Process_Abstract $processor Processer instance
+ *
+ * @return Phergie_Bot Provides a fluent interface
+ */
+ public function setProcessor(Phergie_Process_Abstract $processor)
+ {
+ $this->processor = $processor;
+ return $this;
+ }
+
+ /**
+ * Loads plugins into the plugin handler.
+ *
+ * @return void
+ */
+ protected function loadPlugins()
+ {
+ $config = $this->getConfig();
+ $plugins = $this->getPluginHandler();
+ $ui = $this->getUi();
+
+ $plugins->setAutoload($config['plugins.autoload']);
+ foreach ($config['plugins'] as $name) {
+ try {
+ $plugin = $plugins->addPlugin($name);
+ $ui->onPluginLoad($name);
+ } catch (Phergie_Plugin_Exception $e) {
+ $ui->onPluginFailure($name, $e->getMessage());
+ if (!empty($plugin)) {
+ $plugins->removePlugin($plugin);
+ }
+ }
+ }
+ }
+
+ /**
+ * Configures and establishes connections to IRC servers.
+ *
+ * @return void
+ */
+ protected function loadConnections()
+ {
+ $config = $this->getConfig();
+ $driver = $this->getDriver();
+ $connections = $this->getConnectionHandler();
+ $plugins = $this->getPluginHandler();
+ $ui = $this->getUi();
+
+ foreach ($config['connections'] as $data) {
+ $connection = new Phergie_Connection($data);
+ $connections->addConnection($connection);
+
+ $ui->onConnect($data['host']);
+ $driver->setConnection($connection)->doConnect();
+ $plugins->setConnection($connection);
+ $plugins->onConnect();
+ }
+ }
+
+ /**
+ * Establishes server connections and initiates an execution loop to
+ * continuously receive and process events.
+ *
+ * @return Phergie_Bot Provides a fluent interface
+ */
+ public function run()
+ {
+ set_time_limit(0);
+
+ $timezone = $this->getConfig('timezone', 'UTC');
+ date_default_timezone_set($timezone);
+
+ $ui = $this->getUi();
+ $ui->setEnabled($this->getConfig('ui.enabled'));
+
+ $this->loadPlugins();
+ $this->loadConnections();
+
+ $processor = $this->getProcessor();
+
+ $connections = $this->getConnectionHandler();
+ while (count($connections)) {
+ $processor->handleEvents();
+ }
+
+ $ui->onShutdown();
+
+ return $this;
+ }
+}
--- /dev/null
+<?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;
+ }
+
+ /**
+ * 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]);
+ }
+ }
+ }
+}
--- /dev/null
+<?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;
+}
--- /dev/null
+<?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;
+
+ /**
+ * 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->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->nick,
+ $this->username,
+ $this->host
+ );
+ }
+
+ 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::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 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);
+ }
+ }
+ }
+}
--- /dev/null
+<?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;
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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);
+}
--- /dev/null
+<?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;
+}
--- /dev/null
+<?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 = '')
+ {
+ // 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);
+ }
+
+ $buffer .= ' ' . $args;
+ }
+
+ // Transmit the command over the socket connection
+ fwrite($this->socket, $buffer . "\r\n");
+
+ // 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 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);
+
+ } 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);
+ }
+ }
+
+ // 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':
+ $ctcp = substr(strstr($args, ':'), 1);
+ if (substr($ctcp, 0, 1) === "\x01" && substr($ctcp, -1) === "\x01") {
+ $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($this->getConnection()->getNick(), $args);
+ break;
+
+ default:
+ $cmd = 'ctcp';
+ if ($reply) {
+ $cmd .= 'Response';
+ }
+ $args = array($this->getConnection()->getNick(), $ctcp);
+ break;
+ }
+ } else {
+ $args = $this->parseArguments($args, 2);
+ }
+ 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);
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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;
+}
--- /dev/null
+<?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;
+ }
+
+ /**
+ * 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);
+ }
+}
--- /dev/null
+<?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;
+ }
+
+ /**
+ * 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]);
+ }
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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
+{
+}
--- /dev/null
+<?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);
+ }
+}
--- /dev/null
+<?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;
+}
--- /dev/null
+<?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;
+
+ /**
+ * Returns the short name for the plugin based on its class name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return substr(strrchr(get_class($this), '_'), 1);
+ }
+
+ /**
+ * 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->connection)) {
+ 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()
+ {
+ }
+}
--- /dev/null
+<?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.
+ *
+ * @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
+ */
+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()
+ {
+ if (!$this->getConfig('acl.blacklist')
+ && !$this->getConfig('acl.whitelist')
+ ) {
+ $this->plugins->removePlugin($this);
+ }
+ }
+
+ /**
+ * Checks permission settings and short-circuits event processing for
+ * blacklisted users.
+ *
+ * @return bool FALSE to short-circuit event processing if the user is
+ * blacklisted, TRUE otherwise
+ */
+ public function preEvent()
+ {
+ // Ignore server responses
+ if ($this->event instanceof Phergie_Event_Response) {
+ return true;
+ }
+
+ // Ignore server-initiated events
+ if (!$this->event->isFromUser()) {
+ return true;
+ }
+
+ // Determine whether a whitelist or blacklist is being used
+ $list = $this->getConfig('acl.whitelist');
+ $matches = true;
+ if (!$list) {
+ $list = $this->getConfig('acl.blacklist');
+ $matches = false;
+ }
+
+ // Support host-specific lists
+ $host = $this->connection->getHost();
+ if (isset($list[$host])) {
+ $list = $list[$host];
+ }
+
+ // Short-circuit event processing if appropriate
+ $hostmask = $this->event->getHostmask();
+ foreach ($list as $pattern) {
+ if ($hostmask->matches($pattern)) {
+ return $matches;
+ }
+ }
+
+ // Allow event processing if appropriate
+ return !$matches;
+ }
+}
--- /dev/null
+<?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');
+ }
+ }
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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);
+ }
+ }
+}
--- /dev/null
+<?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;
+ }
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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
+ */
+class Phergie_Plugin_Command extends Phergie_Plugin_Abstract
+{
+ /**
+ * Cache for command lookups used to confirm that methods exist and
+ * parameter counts match
+ *
+ * @var array
+ */
+ protected $methods = array();
+
+ /**
+ * Prefix for command method names
+ *
+ * @var string
+ */
+ protected $methodPrefix = 'onCommand';
+
+ /**
+ * Populates the methods cache.
+ *
+ * @return void
+ */
+ protected function populateMethodCache()
+ {
+ foreach ($this->getPluginHandler() as $plugin) {
+ $reflector = new ReflectionClass($plugin);
+ foreach ($reflector->getMethods() as $method) {
+ $name = $method->getName();
+ if (strpos($name, $this->methodPrefix) === 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();
+ }
+
+ // Get the content of the message
+ $event = $this->getEvent();
+ $msg = trim($event->getText());
+ $prefix = $this->getConfig('command.prefix');
+
+ // Check for the command prefix if one is set and needed
+ if ($prefix && $event->isInChannel()) {
+ if (strpos($msg, $prefix) !== 0) {
+ return;
+ } else {
+ $msg = substr($msg, strlen($prefix));
+ }
+ }
+
+ // Separate the command and arguments
+ $parsed = preg_split('/\s+/', $msg, 2);
+ $method = $this->methodPrefix . ucfirst(strtolower(array_shift($parsed)));
+ $args = count($parsed) ? array_shift($parsed) : '';
+
+ // Check to ensure the command exists
+ 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
+ );
+ }
+ }
+ }
+}
--- /dev/null
+<?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);
+ }
+}
+?>
--- /dev/null
+<?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)) {
+ if ($config['daddy.curses'] && mt_rand(0, 5) === 5) {
+ $msg = $target . ': I am your ' . $m[1] . ', bitch!';
+ } else {
+ $msg = 'You\'re my ' . $m[1] . ', ' . $target . '!';
+ }
+ $this->doPrivmsg($source, $msg);
+ }
+ }
+}
--- /dev/null
+<?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;
+}
--- /dev/null
+<?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_Google
+ * @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_Google
+ */
+
+/**
+ * Provides commands used to access several services offered by Google
+ * including search, translation, weather, maps, and currency and general
+ * value unit conversion.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Google
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Google
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ * @uses Phergie_Plugin_Http pear.phergie.org
+ *
+ * @pluginDesc Provide access to some Google services
+ */
+class Phergie_Plugin_Google extends Phergie_Plugin_Abstract
+{
+
+ /**
+ * HTTP plugin
+ *
+ * @var Phergie_Plugin_Http
+ */
+ protected $http;
+
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $plugins = $this->getPluginHandler();
+ $plugins->getPlugin('Command');
+ $this->http = $plugins->getPlugin('Http');
+ $plugins->getPlugin('Help')->register($this);
+ }
+
+ /**
+ * Returns the first result of a Google search.
+ *
+ * @param string $query Search term
+ *
+ * @return void
+ * @todo Implement use of URL shortening here
+ *
+ * @pluginCmd [query] do a search on google
+ */
+ public function onCommandG($query)
+ {
+ $url = 'http://ajax.googleapis.com/ajax/services/search/web';
+ $params = array(
+ 'v' => '1.0',
+ 'q' => $query
+ );
+ $response = $this->http->get($url, $params);
+ $json = $response->getContent()->responseData;
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $nick = $event->getNick();
+ if ($json->cursor->estimatedResultCount > 0) {
+ $msg
+ = $nick
+ . ': [ '
+ . $json->results[0]->titleNoFormatting
+ . ' ] - '
+ . $json->results[0]->url
+ . ' - More results: '
+ . $json->cursor->moreResultsUrl;
+ $this->doPrivmsg($source, $msg);
+ } else {
+ $msg = $nick . ': No results for this query.';
+ $this->doPrivmsg($source, $msg);
+ }
+ }
+
+ /**
+ * Performs a Google Count search for the given term.
+ *
+ * @param string $query Search term
+ *
+ * @return void
+ *
+ * @pluginCmd [query] Do a search on Google and count the results
+ */
+ public function onCommandGc($query)
+ {
+ $url = 'http://ajax.googleapis.com/ajax/services/search/web';
+ $params = array(
+ 'v' => '1.0',
+ 'q' => $query
+ );
+ $response = $this->http->get($url, $params);
+ $json = $response->getContent()->responseData->cursor;
+ $count = $json->estimatedResultCount;
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $nick = $event->getNick();
+ if ($count) {
+ $msg
+ = $nick . ': ' .
+ number_format($count, 0) .
+ ' estimated results for ' . $query;
+ $this->doPrivmsg($source, $msg);
+ } else {
+ $this->doPrivmsg($source, $nick . ': No results for this query.');
+ }
+ }
+
+ /**
+ * Performs a Google Translate search for the given term.
+ *
+ * @param string $from Language of the search term
+ * @param string $to Language to which the search term should be
+ * translated
+ * @param string $query Term to translate
+ *
+ * @return void
+ *
+ * @pluginCmd [from language] [to language] [text to translate] Do a translation on Google
+ */
+ public function onCommandGt($from, $to, $query)
+ {
+ $url = 'http://ajax.googleapis.com/ajax/services/language/translate';
+ $params = array(
+ 'v' => '1.0',
+ 'q' => $query,
+ 'langpair' => $from . '|' . $to
+ );
+ $response = $this->http->get($url, $params);
+ $json = $response->getContent();
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $nick = $event->getNick();
+ if (empty($json->responseData->translatedText)) {
+ $this->doPrivmsg($source, $nick . ': ' . $json->responseDetails);
+ } else {
+ $this->doPrivmsg(
+ $source,
+ $nick . ': ' . $json->responseData->translatedText
+ );
+ }
+ }
+
+ /**
+ * Performs a Google Weather search for the given term.
+ *
+ * @param string $location Location to search for
+ *
+ * @return void
+ *
+ * @pluginCmd [location] Show the weather for the specified location
+ */
+ public function onCommandGw($location)
+ {
+ $url = 'http://www.google.com/ig/api';
+ $params = array(
+ 'weather' => $location,
+ 'hl' => 'pt-br',
+ 'oe' => 'UTF-8'
+ );
+ $response = $this->http->get($url, $params);
+ $xml = $response->getContent()->weather;
+ $source = $this->getEvent()->getSource();
+ if (!isset($xml->problem_cause)) {
+ $city = $xml->forecast_information->city->attributes()->data[0];
+ $time = $xml->forecast_information->current_date_time->attributes()
+ ->data[0];
+ $condition = $xml->current_conditions->condition->attributes()->data[0];
+ $temp = $xml->current_conditions->temp_c->attributes()->data[0]
+ . '� C';
+ $humidity = $xml->current_conditions->humidity->attributes()->data[0];
+ $wind = $xml->current_conditions->wind_condition->attributes()->data[0];
+ $msg = implode(' - ', array($city, $temp, $condition, $humidity, $wind));
+ $this->doPrivmsg($source, $msg);
+
+ foreach ($xml->forecast_conditions as $key => $linha) {
+ $day = ucfirst($linha->day_of_week->attributes()->data[0]);
+ $min = $linha->low->attributes()->data[0];
+ $max = $linha->high->attributes()->data[0];
+ $condition = $linha->condition->attributes()->data[0];
+ $msg
+ = 'Forecast: ' . $day .
+ ' - Min: ' . $min . '� C' .
+ ' - Max: ' . $max . '� C' .
+ ' - ' . $condition;
+ $this->doPrivmsg($source, $msg);
+ }
+ } else {
+ $this->doPrivmsg($source, $xml->problem_cause->attributes()->data[0]);
+ }
+ }
+
+ /**
+ * Performs a Google Maps search for the given term.
+ *
+ * @param string $location Location to search for
+ *
+ * @return void
+ *
+ * @pluginCmd [location] Get the location from Google Maps to the location specified
+ */
+ public function onCommandGmap($location)
+ {
+ $location = utf8_encode($location);
+ $url = 'http://maps.google.com/maps/geo';
+ $params = array(
+ 'q' => $location,
+ 'output' => 'json',
+ 'gl' => 'br',
+ 'sensor' => 'false',
+ 'oe' => 'utf8',
+ 'mrt' => 'all',
+ 'key' => $this->_config['google.key']
+ );
+ $response = $this->http->get($url, $params);
+ $json = (array) $response->getContent();
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $nick = $event->getNick();
+ if (!empty($json)) {
+ $qtd = count($json['Placemark']);
+ if ($qtd > 1) {
+ if ($qtd <= 3) {
+ foreach ($json['Placemark'] as $places) {
+ $xy = $places['Point']['coordinates'];
+ $address = utf8_decode($places['address']);
+ $url = 'http://maps.google.com/maps?sll=' . $xy[1] . ','
+ . $xy[0] . '&z=15';
+ $msg = $nick . ' -> ' . $address . ' - ' . $url;
+ $this->doPrivmsg($source, $msg);
+ }
+ } else {
+ $msg
+ = $nick .
+ ', there are a lot of places with that query.' .
+ ' Try to be more specific!';
+ $this->doPrivmsg($source, $msg);
+ }
+ } elseif ($qtd == 1) {
+ $xy = $json['Placemark'][0]['Point']['coordinates'];
+ $address = utf8_decode($json['Placemark'][0]['address']);
+ $url = 'http://maps.google.com/maps?sll=' . $xy[1] . ',' . $xy[0]
+ . '&z=15';
+ $msg = $nick . ' -> ' . $address . ' - ' . $url;
+ $this->doPrivmsg($source, $msg);
+ } else {
+ $this->doPrivmsg($source, $nick . ', I found nothing.');
+ }
+ } else {
+ $this->doPrivmsg($source, $nick . ', we have a problem.');
+ }
+ }
+
+ /**
+ * Perform a Google Convert query to convert a value from one metric to
+ * another.
+ *
+ * @param string $value Value to convert
+ * @param string $from Source metric
+ * @param string $to Destination metric
+ *
+ * @return void
+ *
+ * @pluginCmd [value] [currency from] [currency to] Converts a monetary value from one currency to another
+ */
+ public function onCommandGconvert($value, $from, $to)
+ {
+ $url = 'http://www.google.com/finance/converter';
+ $params = array(
+ 'a' => $value,
+ 'from' => $from,
+ 'to' => $to
+ );
+ $response = $this->http->get($url, $params);
+ $contents = $response->getContent();
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $nick = $event->getNick();
+ if ($contents) {
+ preg_match(
+ '#<span class=bld>.*? ' . $to . '</span>#im',
+ $contents,
+ $matches
+ );
+ if (!$matches[0]) {
+ $this->doPrivmsg($source, $nick . ', I can\'t do that.');
+ } else {
+ $str = str_replace('<span class=bld>', '', $matches[0]);
+ $str = str_replace($to . '</span>', '', $str);
+ $text
+ = number_format($value, 2, ',', '.') . ' ' . $from .
+ ' => ' . number_format($str, 2, ',', '.') . ' ' . $to;
+ $this->doPrivmsg($source, $text);
+ }
+ } else {
+ $this->doPrivmsg($source, $nick . ', we had a problem.');
+ }
+ }
+
+ /**
+ * Performs a Google search to convert a value from one unit to another.
+ *
+ * @param string $unit Source metric
+ * @param string $to Value to be converted
+ * @param string $unit2 Destination metric
+ *
+ * @return void
+ *
+ * @pluginCmd [unit] [to] [unit2] Convert a value from one metric to another
+ */
+ public function onCommandConvert($unit, $to, $unit2)
+ {
+ $url = 'http://www.google.com/search?q='
+ . urlencode($unit . ' ' . $to . ' ' . $unit2);
+ $response = $this->http->get($url);
+ $contents = $response->getContent();
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $nick = $event->getNick();
+
+ if (empty($contents)) {
+ $this->doPrivmsg(
+ $target,
+ $nick . ', sorry, I can\'t give you an answer right now.'
+ );
+ return;
+ }
+
+ $doc = new DomDocument;
+ $doc->loadHTML($contents);
+ foreach ($doc->getElementsByTagName('h2') as $element) {
+ if ($element->getAttribute('class') == 'r') {
+ $children = $element->childNodes;
+ $text = str_replace(
+ array(chr(195), chr(151), chr(160)),
+ array('x', '', ' '),
+ $children->item(0)->nodeValue
+ );
+ if ($children->length >= 3) {
+ $text
+ .= '^' . $children->item(1)->nodeValue
+ . $children->item(2)->nodeValue;
+ }
+ }
+ }
+
+ if (isset($text)) {
+ $this->doPrivmsg($source, $nick . ': ' . $text);
+ } else {
+ $this->doPrivmsg($target, $nick . ', sorry I can\'t do that.');
+ }
+ }
+}
--- /dev/null
+<?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 on-demand loading of, iteration over, and access to 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_Plugin_Handler implements IteratorAggregate
+{
+ /**
+ * Current list of plugin instances
+ *
+ * @var array
+ */
+ protected $plugins;
+
+ /**
+ * Paths in which to search for plugin class files
+ *
+ * @var array
+ */
+ protected $paths;
+
+ /**
+ * Flag indicating whether plugin classes should be instantiated on
+ * demand if they are requested but no instance currently exists
+ *
+ * @var bool
+ */
+ protected $autoload;
+
+ /**
+ * Phergie_Config instance that should be passed in to any plugin
+ * instantiated within the handler
+ *
+ * @var Phergie_Config
+ */
+ protected $config;
+
+ /**
+ * Phergie_Event_Handler instance that should be passed in to any plugin
+ * instantiated within the handler
+ *
+ * @var Phergie_Event_Handler
+ */
+ protected $events;
+
+ /**
+ * Constructor to initialize class properties and add the path for core
+ * plugins.
+ *
+ * @param Phergie_Config $config configuration to pass to any
+ * instantiated plugin
+ * @param Phergie_Event_Handler $events event handler to pass to any
+ * instantiated plugin
+ *
+ * @return void
+ */
+ public function __construct(
+ Phergie_Config $config,
+ Phergie_Event_Handler $events
+ ) {
+ $this->config = $config;
+ $this->events = $events;
+
+ $this->plugins = array();
+ $this->paths = array();
+ $this->autoload = false;
+
+ $this->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+ }
+
+
+ /**
+ * Adds a path to search for plugin class files. Paths are searched in
+ * the reverse order in which they are added.
+ *
+ * @param string $path Filesystem directory path
+ * @param string $prefix Optional class name prefix corresponding to the
+ * path
+ *
+ * @return Phergie_Plugin_Handler Provides a fluent interface
+ * @throws Phergie_Plugin_Exception
+ */
+ public function addPath($path, $prefix = '')
+ {
+ if (!is_readable($path)) {
+ throw new Phergie_Plugin_Exception(
+ 'Path "' . $path . '" does not reference a readable directory',
+ Phergie_Plugin_Exception::ERR_DIRECTORY_NOT_READABLE
+ );
+ }
+
+ $this->paths[] = array(
+ 'path' => rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR,
+ 'prefix' => $prefix
+ );
+
+ return $this;
+ }
+
+ /**
+ * Adds a plugin instance to the handler.
+ *
+ * @param string|Phergie_Plugin_Abstract $plugin Short name of the
+ * plugin class or a plugin object
+ * @param array $args Optional array of
+ * arguments to pass to the plugin constructor if a short name is
+ * passed for $plugin
+ *
+ * @return Phergie_Plugin_Abstract New plugin instance
+ */
+ public function addPlugin($plugin, array $args = null)
+ {
+ // If a short plugin name is specified...
+ if (is_string($plugin)) {
+ $index = strtolower($plugin);
+ if (isset($this->plugins[$index])) {
+ return $this->plugins[$index];
+ }
+
+ // Attempt to locate and load the class
+ foreach (array_reverse($this->paths) as $path) {
+ $file = $path['path'] . $plugin . '.php';
+ if (file_exists($file)) {
+ include_once $file;
+ $class = $path['prefix'] . $plugin;
+ if (class_exists($class)) {
+ break;
+ }
+ unset($class);
+ }
+ }
+
+ // If the class can't be found, display an error
+ if (!isset($class)) {
+ throw new Phergie_Plugin_Exception(
+ 'Class file for plugin "' . $plugin . '" cannot be found',
+ Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND
+ );
+ }
+
+ // Check to ensure the class is a plugin class
+ if (!is_subclass_of($class, 'Phergie_Plugin_Abstract')) {
+ $msg
+ = 'Class for plugin "' . $plugin .
+ '" does not extend Phergie_Plugin_Abstract';
+ throw new Phergie_Plugin_Exception(
+ $msg,
+ Phergie_Plugin_Exception::ERR_INCORRECT_BASE_CLASS
+ );
+ }
+
+ // Check to ensure the class can be instantiated
+ $reflection = new ReflectionClass($class);
+ if (!$reflection->isInstantiable()) {
+ throw new Phergie_Plugin_Exception(
+ 'Class for plugin "' . $plugin . '" cannot be instantiated',
+ Phergie_Plugin_Exception::ERR_CLASS_NOT_INSTANTIABLE
+ );
+ }
+
+ // If the class is found, instantiate it
+ if (!empty($args)) {
+ $instance = $reflection->newInstanceArgs($args);
+ } else {
+ $instance = new $class;
+ }
+
+ // Configure and add the instance
+ $instance->setPluginHandler($this);
+ $instance->setConfig($this->config);
+ $instance->setEventHandler($this->events);
+ $instance->onLoad();
+ $this->plugins[$index] = $instance;
+ $plugin = $instance;
+
+ } elseif ($plugin instanceof Phergie_Plugin_Abstract) {
+ // If a plugin instance is specified...
+
+ // Add the plugin instance to the list of plugins
+ $this->plugins[strtolower($plugin->getName())] = $plugin;
+ }
+
+ return $plugin;
+ }
+
+ /**
+ * Adds multiple plugin instances to the handler.
+ *
+ * @param array $plugins List of elements where each is of the form
+ * 'ShortPluginName' or array('ShortPluginName', array($arg1,
+ * ..., $argN))
+ *
+ * @return Phergie_Plugin_Handler Provides a fluent interface
+ */
+ public function addPlugins(array $plugins)
+ {
+ foreach ($plugins as $plugin) {
+ if (is_array($plugin)) {
+ $this->addPlugin($plugin[0], $plugin[1]);
+ } else {
+ $this->addPlugin($plugin);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Removes a plugin instance from the handler.
+ *
+ * @param string|Phergie_Plugin_Abstract $plugin Short name of the
+ * plugin class or a plugin object
+ *
+ * @return Phergie_Plugin_Handler Provides a fluent interface
+ */
+ public function removePlugin($plugin)
+ {
+ if ($plugin instanceof Phergie_Plugin_Abstract) {
+ $plugin = $plugin->getName();
+ }
+ $plugin = strtolower($plugin);
+
+ unset($this->plugins[$plugin]);
+
+ return $this;
+ }
+
+ /**
+ * Returns the corresponding instance for a specified plugin, loading it
+ * if it is not already loaded and autoloading is enabled.
+ *
+ * @param string $name Short name of the plugin class
+ *
+ * @return Phergie_Plugin_Abstract Plugin instance
+ */
+ public function getPlugin($name)
+ {
+ // If the plugin is loaded, return the instance
+ $lower = strtolower($name);
+ if (isset($this->plugins[$lower])) {
+ return $this->plugins[$lower];
+ }
+
+ // If autoloading is disabled, display an error
+ if (!$this->autoload) {
+ $msg
+ = 'Plugin "' . $name . '" has been requested, ' .
+ 'is not loaded, and autoload is disabled';
+ throw new Phergie_Plugin_Exception(
+ $msg,
+ Phergie_Plugin_Exception::ERR_PLUGIN_NOT_LOADED
+ );
+ }
+
+ // If autoloading is enabled, attempt to load the plugin
+ $plugin = $this->addPlugin($name);
+
+ // Return the added plugin
+ return $plugin;
+ }
+
+ /**
+ * Returns the corresponding instances for multiple specified plugins,
+ * loading them if they are not already loaded and autoloading is
+ * enabled.
+ *
+ * @param array $names List of short names of the plugin classes
+ *
+ * @return array Associative array mapping plugin class short names to
+ * corresponding plugin instances
+ */
+ public function getPlugins(array $names)
+ {
+ $plugins = array();
+ foreach ($names as $name) {
+ $plugins[$name] = $this->getPlugin($name);
+ }
+ return $plugins;
+ }
+
+ /**
+ * Returns whether or not at least one instance of a specified plugin
+ * class is loaded.
+ *
+ * @param string $name Short name of the plugin class
+ *
+ * @return bool TRUE if an instance exists, FALSE otherwise
+ */
+ public function hasPlugin($name)
+ {
+ return isset($this->plugins[strtolower($name)]);
+ }
+
+ /**
+ * Sets a flag used to determine whether plugins should be loaded
+ * automatically if they have not been explicitly loaded.
+ *
+ * @param bool $flag TRUE to have plugins autoload (default), FALSE
+ * otherwise
+ *
+ * @return Phergie_Plugin_Handler Provides a fluent interface.
+ */
+ public function setAutoload($flag = true)
+ {
+ $this->autoload = $flag;
+
+ return $this;
+ }
+
+ /**
+ * Returns the value of a flag used to determine whether plugins should
+ * be loaded automatically if they have not been explicitly loaded.
+ *
+ * @return bool TRUE if autoloading is enabled, FALSE otherwise
+ */
+ public function getAutoload()
+ {
+ return $this->autoload;
+ }
+
+ /**
+ * Allows plugin instances to be accessed as properties of the handler.
+ *
+ * @param string $name Short name of the plugin
+ *
+ * @return Phergie_Plugin_Abstract Requested plugin instance
+ */
+ public function __get($name)
+ {
+ return $this->getPlugin($name);
+ }
+
+ /**
+ * Allows plugin instances to be detected as properties of the handler.
+ *
+ * @param string $name Short name of the plugin
+ *
+ * @return bool TRUE if the plugin is loaded, FALSE otherwise
+ */
+ public function __isset($name)
+ {
+ return $this->hasPlugin($name);
+ }
+
+ /**
+ * Allows plugin instances to be removed as properties of handler.
+ *
+ * @param string $name Short name of the plugin
+ *
+ * @return void
+ */
+ public function __unset($name)
+ {
+ $this->removePlugin($name);
+ }
+
+ /**
+ * Returns an iterator for all currently loaded plugin instances.
+ *
+ * @return ArrayIterator
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->plugins);
+ }
+
+ /**
+ * Proxies method calls to all plugins containing the called method. An
+ * individual plugin may short-circuit this process by explicitly
+ * returning FALSE.
+ *
+ * @param string $name Name of the method called
+ * @param array $args Arguments passed in the method call
+ *
+ * @return bool FALSE if a plugin short-circuits processing by returning
+ * FALSE, TRUE otherwise
+ */
+ public function __call($name, array $args)
+ {
+ foreach ($this->plugins as $plugin) {
+ if (call_user_func_array(array($plugin, $name), $args) === false) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
--- /dev/null
+<?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_Help
+ * @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_Help
+ */
+
+/**
+ * Provides access to descriptions of plugins and the commands they provide.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Help
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Help
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ *
+ * @pluginDesc Provides access to plugin help information
+ */
+class Phergie_Plugin_Help extends Phergie_Plugin_Abstract
+{
+
+ /**
+ * Holds the registry of help data indexed by plugin name
+ *
+ * @var array
+ */
+ protected $registry;
+
+ /**
+ * Whether the registry has been alpha sorted
+ *
+ * @var bool
+ */
+ protected $registry_sorted = false;
+
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->getPluginHandler()->getPlugin('Command');
+ $this->register($this);
+ }
+
+ /**
+ * Displays a list of plugins with help information available or
+ * commands available for a specific plugin.
+ *
+ * @param string $plugin Short name of the plugin for which commands
+ * should be returned, else a list of plugins with help
+ * information available is returned
+ *
+ * @return void
+ *
+ * @pluginCmd Show all active plugins with help available
+ * @pluginCmd [plugin] Shows commands line for a specific plugin
+ */
+ public function onCommandHelp($plugin = null)
+ {
+ $nick = $this->getEvent()->getNick();
+
+ if (!$plugin) {
+ // protect from sorting the registry each time help is called
+ if (!$this->registry_sorted) {
+ asort($this->registry);
+ $this->registry_sorted = true;
+ }
+
+ $msg = 'These plugins below have help information available.';
+ $this->doPrivMsg($nick, $msg);
+
+ foreach ($this->registry as $plugin => $data) {
+ $this->doPrivMsg($nick, "{$plugin} - {$data['desc']}");
+ }
+ } else {
+ if (isset($this->getPluginHandler()->{$plugin})
+ && isset($this->registry[strtolower($plugin)]['cmd'])
+ ) {
+ $msg
+ = 'The ' .
+ $plugin .
+ ' plugin exposes the commands shown below.';
+ $this->doPrivMsg($nick, $msg);
+ if ($this->getConfig('command.prefix')) {
+ $msg
+ = 'Note that these commands must be prefixed with "' .
+ $this->getConfig('command.prefix') .
+ '" (without quotes) when issued in a public channel.';
+ $this->doPrivMsg($nick, $msg);
+ }
+
+ foreach ($this->registry[strtolower($plugin)]['cmd']
+ as $cmd => $descs
+ ) {
+ foreach ($descs as $desc) {
+ $this->doPrivMsg($nick, "{$cmd} {$desc}");
+ }
+ }
+
+ } else {
+ $this->doPrivMsg($nick, 'That plugin is not loaded.');
+ }
+ }
+ }
+
+ /**
+ * Sets the description for the plugin instance
+ *
+ * @param Phergie_Plugin_Abstract $plugin plugin instance
+ * @param string $description plugin description
+ *
+ * @return void
+ */
+ public function setPluginDescription(
+ Phergie_Plugin_Abstract $plugin,
+ $description
+ ) {
+ $this->registry[strtolower($plugin->getName())]
+ ['desc'] = $description;
+ }
+
+ /**
+ * Sets the description for the command on the plugin instance
+ *
+ * @param Phergie_Plugin_Abstract $plugin plugin instance
+ * @param string $command from onCommand method
+ * @param string $description command description
+ *
+ * @return void
+ */
+ public function setCommandDescription(
+ Phergie_Plugin_Abstract $plugin,
+ $command,
+ array $description
+ ) {
+ $this->registry[strtolower($plugin->getName())]
+ ['cmd'][$command] = $description;
+ }
+
+ /**
+ * registers the plugin with the help plugin. this will parse the docblocks
+ * for specific annotations that this plugin will respond with when
+ * queried.
+ *
+ * @param Phergie_Plugin_Abstract $plugin plugin instance
+ *
+ * @return void
+ */
+ public function register(Phergie_Plugin_Abstract $plugin)
+ {
+ $class = new ReflectionClass($plugin);
+
+ $annotations = self::parseAnnotations($class->getDocComment());
+ if (isset($annotations['pluginDesc'])) {
+ $this->setPluginDescription(
+ $plugin,
+ join(' ', $annotations['pluginDesc'])
+ );
+ }
+
+ foreach ($class->getMethods() as $method) {
+ if (strpos($method->getName(), 'onCommand') !== false) {
+ $annotations = self::parseAnnotations($method->getDocComment());
+ if (isset($annotations['pluginCmd'])) {
+ $cmd = strtolower(substr($method->getName(), 9));
+ $this->setCommandDescription(
+ $plugin,
+ $cmd,
+ $annotations['pluginCmd']
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ * Taken from PHPUnit/Util/Test.php:436
+ *
+ * PHPUnit
+ *
+ * Copyright (c) 2002-2010, Sebastian Bergmann <sb@sebastian-bergmann.de>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Sebastian Bergmann nor the names of his
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @param string $docblock docblock to parse
+ *
+ * @return array
+ */
+ protected static function parseAnnotations($docblock)
+ {
+ $annotations = array();
+
+ $regex = '/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?$/m';
+
+ if (preg_match_all($regex, $docblock, $matches)) {
+ $numMatches = count($matches[0]);
+
+ for ($i = 0; $i < $numMatches; ++$i) {
+ $annotations[$matches['name'][$i]][] = $matches['value'][$i];
+ }
+ }
+
+ return $annotations;
+ }
+}
--- /dev/null
+<?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_Http
+ * @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_Http
+ */
+
+/**
+ * Provides an HTTP client for plugins to use in contacting web services or
+ * retrieving feeds or web pages.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Http
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Http
+ * @uses extension simplexml optional
+ * @uses extension json optional
+ */
+class Phergie_Plugin_Http extends Phergie_Plugin_Abstract
+{
+ /**
+ * Response to the last executed HTTP request
+ *
+ * @var Phergie_Plugin_Http_Response
+ */
+ protected $response;
+
+ /**
+ * Mapping of content types to handlers for them
+ *
+ * @var array
+ */
+ protected $handlers;
+
+ /**
+ * Initializes the handler lookup table.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->handlers = array(
+ '(?:application|text)/xml(?:;.*)?' => 'simplexml_load_string',
+ '(?:(?:application|text)/(?:x-)?json)|text/javascript.*' => 'json_decode',
+ );
+
+ if (is_array($this->config['http.handlers'])) {
+ $this->handlers = array_merge(
+ $this->handlers,
+ $this->config['http.handlers']
+ );
+ }
+ }
+
+ /**
+ * Sets a handler callback for a content type, which is called when a
+ * response of that content type is received to perform any needed
+ * transformations on the response body content before storing it in the
+ * response object. Note that the calling plugin is responsible for
+ * indicating any dependencies related to specified handler callbacks.
+ *
+ * @param string $type PCRE regular expression (without delimiters) that
+ * matches one or more MIME types
+ * @param callback $callback Callback to execute when a response of a content
+ * type matched by $type is encountered
+ *
+ * @return Phergie_Plugin_Http Provides a fluent interface
+ */
+ public function setHandler($type, $callback)
+ {
+ if (!is_callable($callback)) {
+ throw new Phergie_Plugin_Exception(
+ 'Invalid callback specified',
+ Phergie_Plugin_Exception::ERR_FATAL_ERROR
+ );
+ }
+
+ $this->handlers[$type] = $callback;
+
+ return $this;
+ }
+
+ /**
+ * Supporting method that parses the status line of an HTTP response
+ * message.
+ *
+ * @param string $status Status line
+ *
+ * @return array Associative array containing the HTTP version, response
+ * code, and response description
+ */
+ protected function parseStatusLine($status)
+ {
+ $parts = explode(' ', $status, 3);
+ $parsed = array(
+ 'version' => str_replace('HTTP/', '', $parts[0]),
+ 'code' => $parts[1],
+ 'message' => rtrim($parts[2])
+ );
+ return $parsed;
+ }
+
+ /**
+ * Supporting method that acts as an error handler to intercept HTTP
+ * responses resulting in PHP-level errors.
+ *
+ * @param int $errno Level of the error raised
+ * @param string $errstr Error message
+ * @param string $errfile Name of the file in which the error was raised
+ * @param string $errline Line number on which the error was raised
+ *
+ * @return bool Always returns TRUE to allow normal execution to
+ * continue once this method terminates
+ */
+ protected function handleError($errno, $errstr, $errfile, $errline)
+ {
+ if ($httperr = strstr($errstr, 'HTTP/')) {
+ $parts = $this->parseStatusLine($httperr);
+ $this->response
+ ->setCode($parts['code'])
+ ->setMessage($parts['message']);
+ }
+
+ return true;
+ }
+
+ /**
+ * Supporting method that executes a request and handles the response.
+ *
+ * @param string $url URL to request
+ * @param array $context Associative array of stream context parameters
+ *
+ * @return Phergie_Plugin_Http_Response Object representing the response
+ * resulting from the request
+ */
+ public function request($url, array $context)
+ {
+ $this->response = new Phergie_Plugin_Http_Response;
+
+ $url = (string) $url;
+ $context = stream_context_create(array('http' => $context));
+
+ set_error_handler(array($this, 'handleError'), E_WARNING);
+ $stream = fopen($url, 'r', false, $context);
+ if ($stream) {
+ $meta = stream_get_meta_data($stream);
+ $status = $this->parseStatusLine($meta['wrapper_data'][0]);
+ $code = $status['code'];
+ $message = $status['message'];
+ $headers = array();
+ foreach (array_slice($meta['wrapper_data'], 1) as $header) {
+ list($name, $value) = explode(': ', $header, 2);
+ $headers[$name] = $value;
+ }
+ unset($meta['wrapper_data']);
+
+ $this->response
+ ->setCode($code)
+ ->setMessage($message)
+ ->setHeaders($headers)
+ ->setMeta($meta);
+
+ $body = stream_get_contents($stream);
+ $type = $this->response->getHeaders('content-type');
+ foreach ($this->handlers as $expr => $handler) {
+ if (preg_match('#^' . $expr . '$#i', $type)) {
+ $body = call_user_func($handler, $body);
+ }
+ }
+
+ $this->response->setContent($body);
+ }
+ restore_error_handler();
+
+ return $this->response;
+ }
+
+ /**
+ * Performs a GET request.
+ *
+ * @param string $url URL for the request
+ * @param array $query Optional associative array of parameters
+ * constituting the URL query string if $url has none
+ * @param array $context Optional associative array of additional stream
+ * context parameters
+ *
+ * @return Phergie_Plugin_Http_Response Received response data
+ */
+ public function get($url, array $query = array(), array $context = array())
+ {
+ if (!empty($query)) {
+ $url .= '?' . http_build_query($query);
+ }
+
+ $context['method'] = 'GET';
+
+ return $this->request($url, $context);
+ }
+
+ /**
+ * Performs a HEAD request.
+ *
+ * @param string $url URL for the request
+ * @param array $query Optional associative array of parameters
+ * constituting the URL query string if $url has none
+ * @param array $context Optional associative array of additional stream
+ * context parameters
+ *
+ * @return Phergie_Plugin_Http_Response Received response data
+ */
+ public function head($url, array $query = array(), array $context = array())
+ {
+ if (!empty($query)) {
+ $url .= '?' . http_build_query($query);
+ }
+
+ $context['method'] = 'HEAD';
+
+ return $this->request($url, $context);
+ }
+
+ /**
+ * Performs a POST request.
+ *
+ * @param string $url URL for the request
+ * @param array $query Optional associative array of parameters
+ * constituting the URL query string if $url has none
+ * @param array $post Optional associative array of parameters
+ * constituting the POST request body if it is using the
+ * traditional URL-encoded format
+ * @param array $context Optional associative array of additional stream
+ * context parameters
+ *
+ * @return Phergie_Plugin_Http_Response Received response data
+ */
+ public function post($url, array $query = array(), array $post = array(), array $context = array())
+ {
+ if (!empty($params)) {
+ $url .= '?' . http_build_query($query);
+ }
+
+ $context['method'] = 'POST';
+
+ if (!empty($post)
+ && (!empty($context['header'])
+ xor stripos($context['header'], 'Content-Type'))
+ ) {
+ if (!empty($context['header'])) {
+ $context['header'] .= "\r\n";
+ } else {
+ $context['header'] = '';
+ }
+ $context['header'] .=
+ 'Content-Type: application/x-www-form-urlencoded';
+ $context['content'] = http_build_query($post);
+ }
+
+ return $this->request($url, $context);
+ }
+}
--- /dev/null
+<?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_Http
+ * @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_Http
+ */
+
+/**
+ * Data structure for HTTP response information.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Http
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Http
+ */
+class Phergie_Plugin_Http_Response
+{
+ /**
+ * HTTP response code or 0 if no HTTP response was received
+ *
+ * @var string
+ */
+ protected $code;
+
+ /**
+ * Description of the HTTP response code or the error message if no HTTP
+ * response was received
+ *
+ * @var string
+ */
+ protected $message;
+
+ /**
+ * Content of the response body, decoded for supported content types
+ *
+ * @var mixed
+ */
+ protected $content;
+
+ /**
+ * Associative array mapping response header names to their values
+ *
+ * @var array
+ */
+ protected $headers;
+
+ /**
+ * Associative array containing other metadata about the response
+ *
+ * @var array
+ */
+ protected $meta;
+
+ /**
+ * Sets the HTTP response code.
+ *
+ * @param string $code Response code
+ *
+ * @return Phergie_Plugin_Http_Response Provides a fluent interface
+ */
+ public function setCode($code)
+ {
+ $this->code = $code;
+ return $this;
+ }
+
+ /**
+ * Returns the HTTP response code.
+ *
+ * @return string Response code
+ */
+ public function getCode()
+ {
+ return $this->code;
+ }
+
+ /**
+ * Returns whether the response indicates a client- or server-side error.
+ *
+ * @return bool TRUE if the response indicates an error, FALSE otherwise
+ */
+ public function isError()
+ {
+ switch (substr($this->code, 0, 1)) {
+ case '0':
+ case '4':
+ case '5':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Sets the HTTP response description.
+ *
+ * @param string $message Response description
+ *
+ * @return Phergie_Plugin_Http_Response Provides a fluent interface
+ */
+ public function setMessage($message)
+ {
+ $this->message = $message;
+ return $this;
+ }
+
+ /**
+ * Returns the HTTP response description.
+ *
+ * @return string
+ */
+ public function getMessage()
+ {
+ return $this->message;
+ }
+
+ /**
+ * Sets the content of the response body.
+ *
+ * @param mixed $content Response body content
+ *
+ * @return Phergie_Plugin_Http_Response Provides a fluent interface
+ */
+ public function setContent($content)
+ {
+ $this->content = $content;
+ return $this;
+ }
+
+ /**
+ * Returns the content of the response body.
+ *
+ * @return mixed Response body content, decoded for supported content
+ * types
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * Sets the response headers.
+ *
+ * @param array $headers Associative array of response headers indexed
+ * by header name
+ *
+ * @return Phergie_Plugin_Http_Response Provides a fluent interface
+ */
+ public function setHeaders(array $headers)
+ {
+ $names = array_map('strtolower', array_keys($headers));
+ $values = array_values($headers);
+ $this->headers = array_combine($names, $values);
+ return $this;
+ }
+
+ /**
+ * Returns all response headers or the value of a single specified
+ * response header.
+ *
+ * @param string $name Optional name of a single header for which the
+ * associated value should be returned
+ *
+ * @return array|string Associative array of all header values, a string
+ * containing the value of the header indicated by $name if one
+ * is set, or null if one is not
+ */
+ public function getHeaders($name = null)
+ {
+ if ($name) {
+ $name = strtolower($name);
+ if (empty($this->headers[$name])) {
+ return null;
+ }
+ return $this->headers[$name];
+ }
+ return $this->headers;
+ }
+
+ /**
+ * Sets the response metadata.
+ *
+ * @param array $meta Associative array of response metadata
+ *
+ * @return Phergie_Plugin_Http_Response Provides a fluent interface
+ */
+ public function setMeta(array $meta)
+ {
+ $this->meta = $meta;
+ return $this;
+ }
+
+ /**
+ * Returns all metadata or the value of a single specified metadatum.
+ *
+ * @param string $name Optional name of a single metadatum for which the
+ * associated value should be returned
+ *
+ * @return array|string|null Associative array of all metadata values, a
+ * string containing the value of the metadatum indicated by
+ * $name if one is set, or null if one is not
+ */
+ public function getMeta($name = null)
+ {
+ if ($name) {
+ if (empty($this->meta[$name])) {
+ return null;
+ }
+ return $this->meta[$name];
+ }
+ return $this->meta;
+ }
+}
--- /dev/null
+<?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_Invisible
+ * @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_Invisible
+ */
+
+/**
+ * Marks the bot as invisible when it connects to the server.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Invisible
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Invisible
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_3_2
+ */
+class Phergie_Plugin_Invisible extends Phergie_Plugin_Abstract
+{
+ /**
+ * Marks the bot as invisible when it connects to the server.
+ *
+ * @return void
+ */
+ public function onConnect()
+ {
+ $this->doMode($this->getConnection()->getNick(), '+i');
+ $this->getPluginHandler()->removePlugin($this);
+ }
+}
--- /dev/null
+<?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_Join
+ * @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_Join
+ */
+
+/**
+ * Joins a specified channel on command from a user.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Join
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Join
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ */
+class Phergie_Plugin_Join extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->getPluginHandler()->getPlugin('Command');
+ }
+
+ /**
+ * 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 onCommandJoin($channels, $keys = null)
+ {
+ $this->doJoin($channels, $keys);
+ }
+}
--- /dev/null
+<?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_Part
+ * @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_Part
+ */
+
+/**
+ * Parts a specified channel on command from a user.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Part
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Part
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ */
+class Phergie_Plugin_Part extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->getPluginHandler()->getPlugin('Command');
+ }
+
+ /**
+ * Parts a channel.
+ *
+ * @param string $channels Comma-delimited list of channels to leave
+ *
+ * @return void
+ */
+ public function onCommandPart($channels)
+ {
+ $this->doPart($channels);
+ }
+}
--- /dev/null
+<?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_Php
+ * @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_Php
+ */
+
+/**
+ * Returns information on PHP functions as requested.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Php
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Php
+ * @uses extension pdo
+ * @uses extension pdo_sqlite
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ */
+class Phergie_Plugin_Php extends Phergie_Plugin_Abstract
+{
+ /**
+ * Data source to use
+ *
+ * @var Phergie_Plugin_Php_Source
+ */
+ protected $source;
+
+ /**
+ * Check for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ // @todo find a way to move this to Phergie_Plugin_Php_Source_Local
+ if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
+ $this->fail('PDO and pdo_sqlite extensions must be installed');
+ }
+
+ $this->getPluginHandler()->getPlugin('Command');
+ }
+
+ /**
+ * Initializes the data source.
+ *
+ * @return void
+ */
+ public function onConnect()
+ {
+ // Construct a new data source
+ $this->source = new Phergie_Plugin_Php_Source_Local;
+ }
+
+ /**
+ * Searches the data source for the requested function.
+ *
+ * @param string $functionName Name of the function to search for
+ *
+ * @return void
+ */
+ public function onCommandPhp($functionName)
+ {
+ // Search for the function
+ if ($function = $this->source->findFunction($functionName)) {
+ $msg = 'PHP ' . $function['name'] . ': ' . $function['description'];
+ } else {
+ $msg = 'Search for function ' . $functionName . ' returned no results.';
+ }
+
+ // Return the result to the source
+ $this->doPrivmsg($this->getEvent()->getSource(), $msg);
+ }
+}
--- /dev/null
+<?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_Php
+ * @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_Php
+ */
+
+/**
+ * Data source interface for the Php plugin.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Php
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Php
+ * @uses extension pdo
+ * @uses extension pdo_sqlite
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ */
+interface Phergie_Plugin_Php_Source
+{
+ /**
+ * Searches for a description of the function.
+ *
+ * @param string $function Search pattern to match against the function
+ * name, wildcards supported using %
+ *
+ * @return array|null Associative array containing the function name and
+ * description or NULL if no results are found
+ */
+ public function findFunction($function);
+}
--- /dev/null
+<?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_Php
+ * @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_Php
+ */
+
+/**
+ * Data source for {@see Phergie_Plugin_Php}. This source reads function
+ * descriptions from a file and stores them in a SQLite database. When a
+ * function description is requested, the function is retrieved from the
+ * local database.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Php
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Php
+ * @uses extension pdo
+ * @uses extension pdo_sqlite
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ */
+class Phergie_Plugin_Php_Source_Local implements Phergie_Plugin_Php_Source
+{
+ /**
+ * Local database for storage
+ *
+ * @var PDO
+ */
+ protected $database;
+
+ /**
+ * Source of the PHP function summary
+ *
+ * @var string
+ */
+ protected $url = 'http://cvs.php.net/viewvc.cgi/phpdoc/funcsummary.txt?revision=HEAD';
+
+ /**
+ * Constructor to initialize the data source.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ $path = dirname(__FILE__);
+
+ try {
+ $this->database = new PDO('sqlite:' . $path . '/functions.db');
+ $this->buildDatabase();
+ // @todo Modify this to be rethrown as an appropriate
+ // Phergie_Plugin_Exception and handled in Phergie_Plugin_Php
+ } catch (PDOException $e) {
+ }
+ }
+
+ /**
+ * Searches for a description of the function.
+ *
+ * @param string $function Search pattern to match against the function
+ * name, wildcards supported using %
+ *
+ * @return array|null Associative array containing the function name and
+ * description or NULL if no results are found
+ */
+ public function findFunction($function)
+ {
+ // Remove possible parentheses
+ $split = preg_split('{\(|\)}', $function);
+ $function = (count($split)) ? array_shift($split) : $function;
+
+ // Prepare the database statement
+ $stmt = $this->database->prepare('SELECT `name`, `description` FROM `functions` WHERE `name` LIKE :function');
+ $stmt->execute(array(':function' => $function));
+
+ // Check the results
+ if (count($stmt) > 0) {
+ $result = $stmt->fetch(PDO::FETCH_ASSOC);
+ /**
+ * @todo add class and function URLS
+ * class methods: http://php.net/manual/en/classname.methodname.php
+ * functions: http://php.net/manual/en/function.functionname.php
+ * where '_' is replaced with '-'
+ */
+ return $result;
+ }
+
+ // No results found, return
+ return null;
+ }
+
+ /**
+ * Build the database and parses the function summary file into it.
+ *
+ * @param bool $rebuild TRUE to force a rebuild of the table used to
+ * house function information, FALSE otherwise, defaults to FALSE
+ *
+ * @return void
+ */
+ protected function buildDatabase($rebuild = false)
+ {
+ // Check to see if the functions table exists
+ $table = $this->database->exec("SELECT COUNT(*) FROM `sqlite_master` WHERE `name` = 'functions'");
+
+ // If the table doesn't exist, create it
+ if (!$table) {
+ $this->database->exec('CREATE TABLE `functions` (`name` VARCHAR(255), `description` TEXT)');
+ $this->database->exec('CREATE UNIQUE INDEX `functions_name` ON `functions` (`name`)');
+ }
+
+ // If we created a new table, fill it with data
+ if (!$table || $rebuild) {
+ // Get the contents of the source file
+ // @todo Handle possible error cases better here; the @ operator
+ // shouldn't be needed
+ $contents = @file($this->url, FILE_IGNORE_NEW_LINES + FILE_SKIP_EMPTY_LINES);
+
+ if (!$contents) {
+ return;
+ }
+
+ // Parse the contents
+ $valid = array();
+ $firstPart = '';
+ $lineNumber = 0;
+ foreach ($contents as $line) {
+ // Clean the current line
+ $line = trim($line);
+
+ // Skip comment lines
+ if (0 === strpos($line, '#')) {
+ // reset the line if the current line is odd
+ if (($lineNumber % 2) !== 0) {
+ $lineNumber--;
+ }
+ continue;
+ }
+
+ /*
+ * If the current line is even, it's the first part of the
+ * complete function description ...
+ */
+ if (($lineNumber % 2) === 0) {
+ $firstPart = $line;
+ } else {
+ // ... it's the last part of the complete function description
+ $completeLine = $firstPart . ' ' . $line;
+ $firstPart = '';
+ if (preg_match('{^([^\s]*)[\s]?([^)]*)\(([^\)]*)\)[\sU]+([\sa-zA-Z0-9\.\-_]*)$}', $completeLine, $matches)) {
+ $valid[] = $matches;
+ }
+ }
+ // Up the line number before going to the next line
+ $lineNumber++;
+ }
+ // free up some memory
+ unset($contents);
+
+ // Process the valid matches
+ if (count($valid) > 0) {
+ // Clear the database
+ $this->database->exec('DELETE * FROM `functions`');
+
+ // Prepare the sql statement
+ $stmt = $this->database->prepare('INSERT INTO `functions` (`name`, `description`) VALUES (:name, :description)');
+ $this->database->beginTransaction();
+
+ // Insert the data
+ foreach ($valid as $function) {
+ // Extract function values
+ list( , $retval, $name, $params, $desc) = $function;
+ if (empty($name)) {
+ $name = $retval;
+ $retval = '';
+ }
+ // Reconstruct the complete function line
+ $line = trim($retval . ' ' . $name . '(' . $params . ') - ' . $desc);
+ // Execute the statement
+ $stmt->execute(array(':name' => $name, ':description' => $line));
+ }
+
+ // Commit the changes to the database
+ $this->database->commit();
+ }
+ // free up some more memory
+ unset($valid);
+ }
+ }
+}
--- /dev/null
+<?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_Ping
+ * @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_Ping
+ */
+
+/**
+ * Uses a self CTCP PING to ensure that the client connection has not been
+ * dropped.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Ping
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Ping
+ */
+class Phergie_Plugin_Ping extends Phergie_Plugin_Abstract
+{
+ /**
+ * Timestamp for the last instance in which an event was received
+ *
+ * @var int
+ */
+ protected $lastEvent;
+
+ /**
+ * Timestamp for the last instance in which a PING was sent
+ *
+ * @var int
+ */
+ protected $lastPing;
+
+ /**
+ * Initialize event timestamps upon connecting to the server.
+ *
+ * @return void
+ */
+ public function onConnect()
+ {
+ $this->lastEvent = time();
+ $this->lastPing = null;
+ }
+
+ /**
+ * Updates the timestamp since the last received event when a new event
+ * arrives.
+ *
+ * @return void
+ */
+ public function preEvent()
+ {
+ $this->lastEvent = time();
+ }
+
+ /**
+ * Clears the ping time if a reply is received.
+ *
+ * @return void
+ */
+ public function onPingResponse()
+ {
+ $this->lastPing = null;
+ }
+
+ /**
+ * Performs a self ping if the event threshold has been exceeded or
+ * issues a termination command if the ping threshold has been exceeded.
+ *
+ * @return void
+ */
+ public function onTick()
+ {
+ $time = time();
+ if (!empty($this->lastPing)) {
+ if ($time - $this->lastPing > $this->getConfig('ping.ping', 10)) {
+ $this->doQuit();
+ }
+ } elseif (
+ $time - $this->lastEvent > $this->getConfig('ping.event', 300)
+ ) {
+ $this->lastPing = $time;
+ $this->doPing($this->getConnection()->getNick(), $this->lastPing);
+ }
+ }
+
+ /**
+ * Gets the last ping time
+ * lastPing needs exposing for things such as unit testing
+ *
+ * @return int timestamp of last ping
+ */
+ public function getLastPing()
+ {
+ return $this->lastPing;
+ }
+
+ /**
+ * Set the last ping time
+ * lastPing needs to be exposed for unit testing
+ *
+ * @param int|null $ping timestamp of last ping
+ *
+ * @return self
+ */
+ public function setLastPing($ping = null)
+ {
+ if (null === $ping) {
+ $ping = time();
+ }
+ if (!is_int($ping)) {
+ throw new InvalidArgumentException('$ping must be an integer or null');
+ }
+ $this->lastPing = $ping;
+ return $this;
+ }
+
+ /**
+ * Gets the last event time
+ * lastEvent needs exposing for things such as unit testing
+ *
+ * @return int timestamp of last ping
+ */
+ public function getLastEvent()
+ {
+ return $this->lastEvent;
+ }
+
+ /**
+ * Set the last event time
+ * lastEvent needs to be exposed for unit testing
+ *
+ * @param int|null $event timestamp of last ping
+ *
+ * @return self
+ */
+ public function setLastEvent($event = null)
+ {
+ if (null === $event) {
+ $event = time();
+ }
+ if (!is_int($event)) {
+ throw new InvalidArgumentException('$ping must be an integer or null');
+ }
+ $this->lastEvent = $event;
+ return $this;
+ }
+}
--- /dev/null
+<?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_Pong
+ * @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_Pong
+ */
+
+/**
+ * Responds to PING requests from the server.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Pong
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Pong
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_6_2
+ * @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_6_3
+ */
+class Phergie_Plugin_Pong extends Phergie_Plugin_Abstract
+{
+ /**
+ * Sends a PONG response for each PING request received by the server.
+ *
+ * @return void
+ */
+ public function onPing()
+ {
+ $this->doPong($this->getEvent()->getArgument(0));
+ }
+}
--- /dev/null
+<?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_Prioritize
+ * @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_Prioritize
+ */
+
+/**
+ * Prioritizes events such that they are executed in order from least to most
+ * destructive.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Prioritize
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Prioritize
+ */
+class Phergie_Plugin_Prioritize extends Phergie_Plugin_Abstract
+{
+ /**
+ * Event types ordered by priority of execution
+ *
+ * @var array
+ */
+ protected $priority = array(
+ 'raw',
+ 'pass',
+ 'user',
+ 'ping',
+ 'pong',
+ 'notice',
+ 'join',
+ 'list',
+ 'names',
+ 'version',
+ 'stats',
+ 'links',
+ 'time',
+ 'trace',
+ 'admin',
+ 'info',
+ 'who',
+ 'whois',
+ 'whowas',
+ 'mode',
+ 'privmsg',
+ 'action',
+ 'nick',
+ 'topic',
+ 'invite',
+ 'kill',
+ 'part',
+ 'quit'
+ );
+
+ /**
+ * Prioritizes events from least to most destructive.
+ *
+ * @return void
+ */
+ public function preDispatch()
+ {
+ $events = $this->getEventHandler();
+
+ // Categorize events by type
+ $categorized = array();
+ foreach ($events as $event) {
+ $type = $event->getType();
+ if (!isset($categorized[$type])) {
+ $categorized[$type] = array();
+ }
+ $categorized[$type][] = $event;
+ }
+
+ // Order events by type from least to most destructive
+ $types = array_intersect($this->priority, array_keys($categorized));
+ $prioritized = array();
+ foreach ($types as $type) {
+ $prioritized = array_merge($prioritized, $categorized[$type]);
+ }
+
+ // Replace the original events array with the prioritized one
+ $events->replaceEvents($prioritized);
+ }
+}
--- /dev/null
+<?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_Puppet
+ * @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_Puppet
+ */
+
+/**
+ * Allows a user to effectively speak and act as the bot.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Puppet
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Puppet
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ */
+class Phergie_Plugin_Puppet extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->getPluginHandler()->getPlugin('Command');
+ }
+
+ /**
+ * Handles a request for the bot to repeat a given message in a specified
+ * channel.
+ *
+ * <code>say #chan message</code>
+ *
+ * @param string $channel Name of the channel
+ * @param string $message Message to repeat
+ *
+ * @return void
+ */
+ public function onCommandSay($channel, $message)
+ {
+ $this->doPrivmsg($channel, $message);
+ }
+
+ /**
+ * Handles a request for the bot to repeat a given action in a specified
+ * channel.
+ *
+ * <code>act #chan action</code>
+ *
+ * @param string $channel Name of the channel
+ * @param string $action Action to perform
+ *
+ * @return void
+ */
+ public function onCommandAct($channel, $action)
+ {
+ $this->doAction($channel, $action);
+ }
+
+ /**
+ * Handles a request for the bot to send the server a raw message
+ *
+ * <code>raw message</code>
+ *
+ * @param string $message Message to send
+ *
+ * @return void
+ */
+ public function onCommandRaw($message)
+ {
+ $this->doRaw($message);
+ }
+}
--- /dev/null
+<?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_Quit
+ * @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_Quit
+ */
+
+/**
+ * Terminates the current connection upon command.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Quit
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Quit
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ *
+ * @pluginDesc Terminates the current connection upon command.
+ */
+class Phergie_Plugin_Quit extends Phergie_Plugin_Abstract
+{
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->getPluginHandler()->getPlugin('Command');
+ $help = $this->getPluginHandler()->getPlugin('Help');
+ $help->register($this);
+ }
+
+ /**
+ * Issues a quit command when a message is received requesting that the
+ * bot terminate the current connection.
+ *
+ * @return void
+ *
+ * @pluginCmd terminates the connection
+ */
+ public function onCommandQuit()
+ {
+ $this->doQuit('Requested by ' . $this->getEvent()->getNick());
+ }
+}
--- /dev/null
+<?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_Remind
+ * @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_Remind
+ */
+
+/**
+ * Parses and logs messages that should be relayed to other users the next time
+ * the recipient is active on the same channel.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Remind
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Drink
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ * @uses Phergie_Plugin_Helper_Time pear.phergie.org
+ */
+class Phergie_Plugin_Remind extends Phergie_Plugin_Abstract
+{
+ /**
+ * Number of reminders to show in public.
+ */
+ const PUBLIC_REMINDERS = 3;
+
+ /**
+ * PDO resource for a SQLite database containing the reminders.
+ *
+ * @var resource
+ */
+ protected $db;
+
+ /**
+ * Flag that indicates whether or not to use an in-memory reminder list.
+ *
+ * @var bool
+ */
+ protected $keepListInMemory = true;
+
+ /**
+ * In-memory store for pending reminders.
+ */
+ protected $msgStorage = array();
+
+ /**
+ * Check for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->getPluginHandler()->getPlugin('Command');
+ $path = dirname(__FILE__) . '/reminder.db';
+
+ if (isset($this->config['remind.use_memory'])) {
+ $this->keepListInMemory = (bool)$this->config['remind.use_memory'];
+ }
+
+ try {
+ $this->db = new PDO('sqlite:' . $path);
+ $this->createTables();
+ } catch (PDO_Exception $e) {
+ throw new Phergie_Plugin_Exception($e->getMessage());
+ }
+ }
+
+ /**
+ * Intercepts a message and processes any contained recognized commands.
+ *
+ * @return void
+ */
+ public function onPrivmsg()
+ {
+ $source = $this->getEvent()->getSource();
+ $nick = $this->getEvent()->getNick();
+
+ $this->deliverReminders($source, $nick);
+ }
+
+ /**
+ * Handle reminder requests
+ *
+ * @param string $recipient recipient of the message
+ * @param string $message message to tell the recipient
+ *
+ * @return void
+ * @see handleRemind()
+ */
+ public function onCommandTell($recipient, $message)
+ {
+ $this->handleRemind($recipient, $message);
+ }
+
+ /**
+ * Handle reminder requests
+ *
+ * @param string $recipient recipient of the message
+ * @param string $message message to tell the recipient
+ *
+ * @return void
+ * @see handleRemind()
+ */
+ public function onCommandAsk($recipient, $message)
+ {
+ $this->handleRemind($recipient, $message);
+ }
+
+ /**
+ * Handle reminder requests
+ *
+ * @param string $recipient recipient of the message
+ * @param string $message message to tell the recipient
+ *
+ * @return void
+ * @see handleRemind()
+ */
+ public function onCommandRemind($recipient, $message)
+ {
+ $this->handleRemind($recipient, $message);
+ }
+
+ /**
+ * Handles the tell/remind command (stores the message)
+ *
+ * @param string $recipient name of the recipient
+ * @param string $message message to store
+ *
+ * @return void
+ */
+ protected function handleRemind($recipient, $message)
+ {
+ $source = $this->getEvent()->getSource();
+ $nick = $this->getEvent()->getNick();
+
+ if (!$this->getEvent()->isInChannel()) {
+ $this->doPrivmsg($source, 'Reminders must be requested in-channel.');
+ return;
+ }
+
+ $q = $this->db->prepare(
+ 'INSERT INTO remind
+ (
+ time,
+ channel,
+ recipient,
+ sender,
+ message
+ )
+ VALUES
+ (
+ :time,
+ :channel,
+ :recipient,
+ :sender,
+ :message
+ )'
+ );
+ try {
+ $q->execute(
+ array(
+ 'time' => date(DATE_RFC822),
+ 'channel' => $source,
+ 'recipient' => strtolower($recipient),
+ 'sender' => strtolower($nick),
+ 'message' => $message
+ )
+ );
+ } catch (PDOException $e) {
+ }
+
+ if ($rowid = $this->db->lastInsertId()) {
+ $this->doPrivmsg($source, 'ok, ' . $nick . ', message stored');
+ } else {
+ $this->doPrivmsg(
+ $source,
+ $nick . ': bad things happened. Message not saved.'
+ );
+ return;
+ }
+
+ if ($this->keepListInMemory) {
+ $this->msgStorage[$source][strtolower($recipient)] = $rowid;
+ }
+ }
+
+ /**
+ * Determines if the user has pending reminders, and if so, delivers them.
+ *
+ * @param string $channel channel to check
+ * @param string $nick nick to check
+ *
+ * @return void
+ */
+ protected function deliverReminders($channel, $nick)
+ {
+ if ($channel[0] != '#') {
+ // private message, not a channel, so don't check
+ return;
+ }
+
+ // short circuit if there's no message in memory (if allowed)
+ if ($this->keepListInMemory
+ && !isset($this->msgStorage[$channel][strtolower($nick)])
+ ) {
+ return;
+ }
+
+ // fetch and deliver messages
+ $reminders = $this->fetchMessages($channel, $nick);
+ if (count($reminders) > self::PUBLIC_REMINDERS) {
+ $msgs = array_slice($reminders, 0, self::PUBLIC_REMINDERS);
+ $privmsgs = array_slice($reminders, self::PUBLIC_REMINDERS);
+ } else {
+ $msgs = $reminders;
+ $privmsgs = false;
+ }
+
+ foreach ($msgs as $msg) {
+ $ts = new Phergie_Plugin_Helper_Time($msg['time']);
+ $formatted = sprintf(
+ '%s: (from %s, %s ago) %s',
+ $nick, $msg['sender'], $ts->getCountdown(), $msg['message']
+ );
+ $this->doPrivmsg($channel, $formatted);
+ $this->deleteMessage($msg['rowid'], $channel, $nick);
+ }
+
+ if ($privmsgs) {
+ foreach ($privmsgs as $msg) {
+ $ts = new Phergie_Plugin_Helper_Time($msg['time']);
+ $formatted = sprintf(
+ 'from %s, %s ago: %s',
+ $msg['sender'], $ts->getCountdown(), $msg['message']
+ );
+ $this->doPrivmsg($nick, $formatted);
+ $this->deleteMessage($msg['rowid'], $channel, $nick);
+ }
+ $formatted = sprintf(
+ '%s: (%d more messages sent in private.)',
+ $nick, count($privmsgs)
+ );
+ $this->doPrivmsg($channel, $formatted);
+ }
+ }
+
+ /**
+ * Get pending messages (for a specific channel/recipient)
+ *
+ * @param string $channel channel on which to check for pending messages
+ * @param string $recipient user for which to check pending messages
+ *
+ * @return array of records
+ */
+ protected function fetchMessages($channel = null, $recipient = null)
+ {
+ if ($channel) {
+ $qClause = 'WHERE channel = :channel AND recipient LIKE :recipient';
+ $params = compact('channel', 'recipient');
+ } else {
+ $qClause = '';
+ $params = array();
+ }
+ $q = $this->db->prepare(
+ 'SELECT rowid, channel, sender, recipient, time, message
+ FROM remind ' . $qClause
+ );
+ $q->execute($params);
+ return $q->fetchAll();
+ }
+
+ /**
+ * Deletes a delivered message
+ *
+ * @param int $rowid ID of the message to delete
+ * @param string $channel message's channel
+ * @param string $nick message's recipient
+ *
+ * @return void
+ */
+ protected function deleteMessage($rowid, $channel, $nick)
+ {
+ $nick = strtolower($nick);
+ $q = $this->db->prepare('DELETE FROM remind WHERE rowid = :rowid');
+ $q->execute(array('rowid' => $rowid));
+
+ if ($this->keepListInMemory) {
+ if (isset($this->msgStorage[$channel][$nick])
+ && $this->msgStorage[$channel][$nick] == $rowid
+ ) {
+ unset($this->msgStorage[$channel][$nick]);
+ }
+ }
+ }
+
+ /**
+ * Determines if a table exists
+ *
+ * @param string $name Table name
+ *
+ * @return bool
+ */
+ protected function haveTable($name)
+ {
+ $sql = 'SELECT COUNT(*) FROM sqlite_master WHERE name = '
+ . $this->db->quote($name);
+ return (bool) $this->db->query($sql)->fetchColumn();
+ }
+
+ /**
+ * Creates the database table(s) (if they don't exist)
+ *
+ * @return void
+ */
+ protected function createTables()
+ {
+ if (!$this->haveTable('remind')) {
+ $this->db->exec(
+ 'CREATE TABLE
+ remind
+ (
+ time INTEGER,
+ channel TEXT,
+ recipient TEXT,
+ sender TEXT,
+ message TEXT
+ )'
+ );
+ }
+ }
+
+ /**
+ * Populates the in-memory cache of pending reminders
+ *
+ * @return void
+ */
+ protected function populateMemory()
+ {
+ if (!$this->keepListInMemory) {
+ return;
+ }
+ foreach ($this->fetchMessages() as $msg) {
+ $this->msgStorage[$msg['channel']][$msg['recipient']] = $msg['rowid'];
+ }
+ }
+}
--- /dev/null
+<?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_TerryChay
+ * @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_TerryChay
+ */
+
+/**
+ * Parses incoming messages for the words "Terry Chay" or tychay and responds
+ * with a random Terry fact retrieved from the Chayism web service.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_TerryChay
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_TerryChay
+ * @uses Phergie_Plugin_Http pear.phergie.org
+ */
+class Phergie_Plugin_TerryChay extends Phergie_Plugin_Abstract
+{
+ /**
+ * URL to the web service
+ *
+ * @const string
+ */
+ const URL = 'http://phpdoc.info/chayism/';
+
+ /**
+ * HTTP plugin
+ *
+ * @var Phergie_Plugin_Http
+ */
+ protected $http;
+
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $this->http = $this->getPluginHandler()->getPlugin('Http');
+ }
+
+ /**
+ * Fetches a chayism.
+ *
+ * @return string|bool Fetched chayism or FALSE if the operation failed
+ */
+ public function getChayism()
+ {
+ return $this->http->get(self::URL)->getContent();
+ }
+
+ /**
+ * Parses incoming messages for "Terry Chay" and related variations and
+ * responds with a chayism.
+ *
+ * @return void
+ */
+ public function onPrivmsg()
+ {
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $message = $event->getText();
+ $pattern
+ = '{^(' . preg_quote($this->getConfig('command.prefix')) .
+ '\s*)?.*(terry\s+chay|tychay)}ix';
+
+ if (preg_match($pattern, $message)
+ && $fact = $this->getChayism()
+ ) {
+ $this->doPrivmsg($source, 'Fact: ' . $fact);
+ }
+ }
+
+ /**
+ * Parses incoming CTCP request for "Terry Chay" and related variations
+ * and responds with a chayism.
+ *
+ * @return void
+ */
+ public function onCtcp()
+ {
+ $event = $this->getEvent();
+ $source = $event->getSource();
+ $ctcp = $event->getArgument(1);
+
+ if (preg_match('({terry[\s_+-]*chay}|tychay)ix', $ctcp)
+ && $fact = $this->getChayism()
+ ) {
+ $this->doCtcpReply($source, 'TERRYCHAY', $fact);
+ }
+ }
+}
--- /dev/null
+<?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_TheFuckingWeather
+ * @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_TheFuckingWeather
+ */
+
+/**
+ * Detects and responds to requests for current weather conditions in a
+ * particular location using data from a web service.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_TheFuckingWeather
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_TheFuckingWeather
+ * @link http://thefuckingweather.com
+ * @uses Phergie_Plugin_Command pear.phergie.org
+ * @uses Phergie_Plugin_Http pear.phergie.org
+ *
+ * @pluginDesc Detects and responds to requests for current weather
+ * conditions in a particular location.
+ */
+
+class Phergie_Plugin_TheFuckingWeather extends Phergie_Plugin_Abstract
+{
+ /**
+ * HTTP plugin
+ *
+ * @var Phergie_Plugin_Http
+ */
+ protected $http = null;
+
+ /**
+ * Base API URL
+ *
+ * @var string
+ */
+ protected $url = 'http://www.thefuckingweather.com/?zipcode=';
+
+ /**
+ * Checks for dependencies.
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ $pluginHandler = $this->getPluginHandler();
+ $pluginHandler->getPlugin('Command');
+ $this->http = $pluginHandler->getPlugin('Http');
+ }
+
+ /**
+ * Returns the weather from the specified location.
+ *
+ * @param string $location Location term
+ *
+ * @return void
+ * @todo Implement use of URL shortening here
+ * @pluginCmd [location] Detects and responds to requests for current
+ * weather conditions in a particular location.
+ */
+ public function onCommandThefuckingweather($location)
+ {
+ $source = $this->getEvent()->getSource();
+ $target = $this->getEvent()->getNick();
+ $out = $this->getWeather($location);
+ if (!$out) {
+ $this->doNotice($source, $out);
+ } else {
+ $this->doPrivmsg($source, $target . ': ' . $out);
+ }
+ }
+
+ /**
+ * Alias for TheFuckingWeather command.
+ *
+ * @param string $location Location term
+ *
+ * @return void
+ * @pluginCmd [location] Alias for thefuckingweather command.
+ */
+ public function onCommandTfw($location)
+ {
+ $this->onCommandThefuckingweather($location);
+ }
+
+ /**
+ * Get the necessary content and returns the search result.
+ *
+ * @param string $location Location term
+ *
+ * @return string|bool Search result or FALSE if none is found
+ * @todo Try to optimize pregs
+ */
+ protected function getWeather($location)
+ {
+ $url = $this->url . urlencode($location);
+ $response = $this->http->get($url);
+ $content = $response->getContent();
+
+ preg_match_all(
+ '#<div><span class="small">(.*?)<\/span><\/div>#im',
+ $content, $matches
+ );
+ $location = $matches[1][0];
+
+ if (!empty($location)) {
+ preg_match_all(
+ '#<div class="large" >(.*?)<br \/>#im',
+ $content, $matches
+ );
+ $temp_numb = (int) $matches[1][0];
+ $temp_numb .= ' F / ' . round(($temp_numb - 32) / 1.8, 0) . ' C?!';
+
+ preg_match_all(
+ '#<br \/>(.*?)<\/div><div id="remark"><br \/>#im',
+ $content, $matches
+ );
+ $temp_desc = $matches[1][0];
+
+ preg_match_all(
+ '#<div id="remark"><br \/>\n<span>(.*?)<\/span><\/div>#im',
+ $content, $matches
+ );
+ $remark = $matches[1][0];
+
+ $result = "{$location}: {$temp_numb} {$temp_desc} ({$remark})";
+ $result = preg_replace('/</', ' <', $result);
+ $result = strip_tags($result);
+ return html_entity_decode($result);
+ } else {
+ return 'No fucking clue where that is.';
+ }
+ }
+}
--- /dev/null
+<?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_Time
+ * @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_Time
+ */
+
+/**
+ * Helper plugin to assist other plugins with time manipulation, display.
+ *
+ * Any shared time-related code should go into this class.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Time
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Time
+ */
+class Phergie_Plugin_Time extends Phergie_Plugin_Abstract
+{
+ /**
+ * Returns the time interval between the current time and a given
+ * timestamp.
+ *
+ * @param string $timestamp Timestamp compatible with strtotime()
+ *
+ * @return string
+ */
+ public function getCountdown($timestamp)
+ {
+ $time = time() - strtotime($timestamp);
+ $return = array();
+
+ $days = floor($time / 86400);
+ if ($days > 0) {
+ $return[] = $days . 'd';
+ $time %= 86400;
+ }
+
+ $hours = floor($time / 3600);
+ if ($hours > 0) {
+ $return[] = $hours . 'h';
+ $time %= 3600;
+ }
+
+ $minutes = floor($time / 60);
+ if ($minutes > 0) {
+ $return[] = $minutes . 'm';
+ $time %= 60;
+ }
+
+ if ($time > 0 || count($return) <= 0) {
+ $return[] = ($time > 0 ? $time : '0') . 's';
+ }
+
+ return implode(' ', $return);
+ }
+}
--- /dev/null
+<?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_Twitter
+ * @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_Twitter
+ */
+
+/**
+ * These requires are for library code, so they don't fit Autoload's normal
+ * conventions.
+ *
+ * @link http://github.com/scoates/simpletweet
+ */
+require dirname(__FILE__) . '/Twitter/twitter.class.php';
+require dirname(__FILE__) . '/Twitter/laconica.class.php';
+
+/**
+ * Twitter plugin; Allows tweet (if configured) and twitter commands
+ *
+ * Usage:
+ * tweet text to tweet
+ * (sends a message to twitter and Phergie will give you the link)
+ * twitter username
+ * (fetches and displays the last tweet by @username)
+ * twitter username 3
+ * (fetches and displays the third last tweet by @username)
+ * twitter 1234567
+ * (fetches and displays tweet number 1234567)
+ * http://twitter.com/username/statuses/1234567
+ * (same as `twitter 1234567`)
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Twitter
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Twitter
+ * @uses Phergie_Plugin_Time pear.phergie.org
+ */
+class Phergie_Plugin_Twitter extends Phergie_Plugin_Abstract
+{
+ /**
+ * Twitter object (from Simpletweet)
+ */
+ protected $twitter;
+
+ /**
+ * Twitter user
+ */
+ protected $twitteruser = null;
+
+ /**
+ * Password
+ */
+ protected $twitterpassword = null;
+
+ /**
+ * Allow only admins to tweet
+ */
+ protected $requireAdmin = true;
+
+ /**
+ * Register with the URL plugin, if possible
+ *
+ * @return void
+ */
+ public function onConnect()
+ {
+ if ($url = $this->getPluginHandler()->getPlugin('Url')) {
+ $url->registerRenderer($this);
+ }
+ }
+
+ /**
+ * Initialize (set up configuration vars)
+ *
+ * @return void
+ */
+ public function onLoad()
+ {
+ // see if tweetrequireadmin defined in config
+ if (isset($this->config['twitter.tweetrequireadmin'])
+ && $req = $this->config['twitter.tweetrequireadmin']
+ ) {
+ // if so, override default
+ $this->requireAdmin = $req;
+ }
+ if (!isset($this->config['twitter.class'])
+ || !$twitterClass = $this->config['twitter.class']
+ ) {
+ $twitterClass = 'Twitter';
+ }
+
+ $this->twitteruser = $this->config['twitter.user'];
+ $this->twitterpassword = $this->config['twitter.password'];
+ $url = $this->config['twitter.url'];
+
+ $this->twitter = new $twitterClass(
+ $this->twitteruser,
+ $this->twitterpassword,
+ $url
+ );
+
+ }
+
+ /**
+ * Fetches the associated tweet and relays it to the channel
+ *
+ * @param string $tweeter if numeric the tweet number/id, otherwise the
+ * twitter user name (optionally prefixed with @)
+ * @param int $num optional tweet number for this user (number of
+ * tweets ago)
+ *
+ * @return void
+ */
+ public function onCommandTwitter($tweeter = null, $num = 1)
+ {
+ $source = $this->getEvent()->getSource();
+ if (is_numeric($tweeter)) {
+ $tweet = $this->twitter->getTweetByNum($tweeter);
+ } else if (is_null($tweeter) && $this->twitteruser) {
+ $tweet = $this->twitter->getLastTweet($this->twitteruser, 1);
+ } else {
+ $tweet = $this->twitter->getLastTweet(ltrim($tweeter, '@'), $num);
+ }
+ if ($tweet) {
+ $this->doPrivmsg($source, $this->formatTweet($tweet));
+ }
+ }
+
+ /**
+ * Sends a tweet to Twitter as the configured user
+ *
+ * @param string $txt the text to tweet
+ *
+ * @return void
+ */
+ public function onCommandTweet($txt)
+ {
+ echo "Tweet!\n";
+ $nick = $this->getEvent()->getNick();
+ if (!$this->twitteruser) {
+ return;
+ }
+ if ($this->requireAdmin && !$this->fromAdmin(true)) {
+ return;
+ }
+ $source = $this->getEvent()->getSource();
+ if ($tweet = $this->twitter->sendTweet($txt)) {
+ $this->doPrivmsg(
+ $source, 'Tweeted: '
+ . $this->twitter->getUrlOutputStatus($tweet)
+ );
+ } else {
+ $this->doNotice($nick, 'Tweet failed');
+ }
+ }
+
+ /**
+ * Formats a Tweet into a message suitable for output
+ *
+ * @param object $tweet JSON-decoded tweet object from Twitter
+ * @param bool $includeUrl whether or not to include the URL in the
+ * formatted output
+ *
+ * @return string
+ */
+ protected function formatTweet(StdClass $tweet, $includeUrl = true)
+ {
+ $ts = $this->plugins->time->getCountDown($tweet->created_at);
+ $out = '<@' . $tweet->user->screen_name .'> '. $tweet->text
+ . ' - ' . $ts . ' ago';
+ if ($includeUrl) {
+ $out .= ' (' . $this->twitter->getUrlOutputStatus($tweet) . ')';
+ }
+ return $out;
+ }
+
+ /**
+ * Renders a URL
+ *
+ * @param array $parsed parse_url() output for the URL to render
+ *
+ * @return bool
+ */
+ public function renderUrl(array $parsed)
+ {
+ if ($parsed['host'] != 'twitter.com'
+ && $parsed['host'] != 'www.twitter.com'
+ ) {
+ // unable to render non-twitter URLs
+ return false;
+ }
+
+ $source = $this->getEvent()->getSource();
+
+ if (preg_match('#^/(.*?)/status(es)?/([0-9]+)$#', $parsed['path'], $matches)
+ ) {
+ $tweet = $this->twitter->getTweetByNum($matches[3]);
+ if ($tweet) {
+ $this->doPrivmsg($source, $this->formatTweet($tweet, false));
+ }
+ return true;
+ }
+
+ // if we get this far, we haven't satisfied the URL, so bail:
+ return false;
+
+ }
+}
--- /dev/null
+<?php
+/**
+ * Sean's Simple Twitter Library - Laconica extension
+ *
+ * Copyright 2008, Sean Coates
+ * Usage of the works is permitted provided that this instrument is retained
+ * with the works, so that any entity that uses the works is notified of this
+ * instrument.
+ * DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
+ * ( Fair License - http://www.opensource.org/licenses/fair.php )
+ * Short license: do whatever you like with this.
+ *
+ */
+class Twitter_Laconica extends Twitter {
+
+ /**
+ * Constructor; sets up configuration.
+ *
+ * @param string $user Laconica user name; null for limited read-only access
+ * @param string $pass Laconica password; null for limited read-only access
+ * @param string $baseUrl Base URL of Laconica install. Defaults to identi.ca
+ */
+ public function __construct($user=null, $pass=null, $baseUrl = 'http://identi.ca/') {
+ $this->baseUrl = $baseUrl;
+ parent::__construct($user, $pass);
+ }
+
+ /**
+ * Returns the base API URL
+ */
+ protected function getUrlApi() {
+ return $this->baseUrlFull . 'api/';
+ }
+
+ /**
+ * Output URL: status
+ */
+ public function getUrlOutputStatus(StdClass $tweet) {
+ return $this->baseUrl . 'notice/' . urlencode($tweet->id);
+ }
+}
--- /dev/null
+<?php
+/**
+ * Sean's Simple Twitter Library
+ *
+ * Probably a little more or a little less than you need.
+ *
+ * Copyright 2008, Sean Coates
+ * Usage of the works is permitted provided that this instrument is retained
+ * with the works, so that any entity that uses the works is notified of this
+ * instrument.
+ * DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
+ * ( Fair License - http://www.opensource.org/licenses/fair.php )
+ * Short license: do whatever you like with this.
+ *
+ * komode: le=unix language=php codepage=utf8 tab=4 notabs indent=4
+ */
+class Twitter {
+
+ /**
+ * Base URL for Twitter API
+ *
+ * Do not specify user/password in URL
+ */
+ protected $baseUrl = 'http://twitter.com/';
+
+ /**
+ * Full base URL (includes user/pass)
+ *
+ * (created in Init)
+ */
+ protected $baseUrlFull = null;
+
+ /**
+ * Twitter API user
+ */
+ protected $user;
+
+ /**
+ * Twitter API password
+ */
+ protected $pass;
+
+ /**
+ * Constructor; sets up configuration.
+ *
+ * @param string $user Twitter user name; null for limited read-only access
+ * @param string $pass Twitter password; null for limited read-only access
+ */
+ public function __construct($user=null, $pass=null) {
+ $this->baseUrlFull = $this->baseUrl;
+ if (null !== $user) {
+ // user is defined, so use it in the URL
+ $this->user = $user;
+ $this->pass = $pass;
+ $parsed = parse_url($this->baseUrl);
+ $this->baseUrlFull = $parsed['scheme'] . '://' . $this->user . ':' .
+ $this->pass . '@' . $parsed['host'];
+ // port (optional)
+ if (isset($parsed['port']) && is_numeric($parsed['port'])) {
+ $this->baseUrlFull .= ':' . $parsed['port'];
+ }
+ // append path (default: /)
+ if (isset($parsed['path'])) {
+ $this->baseUrlFull .= $parsed['path'];
+ } else {
+ $this->baseUrlFull .= '/';
+ }
+ }
+ }
+
+ /**
+ * Fetches a tweet by its number/id
+ *
+ * @param int $num the tweet id/number
+ * @return string (null on failure)
+ */
+ public function getTweetByNum($num) {
+ if (!is_numeric($num)) {
+ return;
+ }
+ $tweet = json_decode(file_get_contents($this->getUrlStatus($num)));
+ return $tweet;
+ }
+
+ /**
+ * Reads [last] tweet from user
+ *
+ * @param string $tweeter the tweeter username
+ * @param int $num this many tweets ago (1 = current tweet)
+ * @return string (false on failure)
+ */
+ public function getLastTweet($tweeter, $num = 1)
+ {
+ $source = json_decode(file_get_contents($this->getUrlUserTimeline($tweeter)));
+ if ($num > count($source)) {
+ return false;
+ }
+ $tweet = $source[$num - 1];
+ if (!isset($tweet->user->screen_name) || !$tweet->user->screen_name) {
+ return false;
+ }
+ return $tweet;
+ }
+
+ /**
+ * fetches mentions for a user
+ */
+ public function getMentions($sinceId=null, $count=20) {
+ return json_decode(file_get_contents($this->getUrlMentions($sinceId, $count)));
+ }
+
+ /**
+ * Fetches followers for a user
+ */
+ public function getFollowers($cursor=-1) {
+ return json_decode(file_get_contents($this->getUrlFollowers($cursor)));
+ }
+
+ /**
+ * Follow a userid
+ */
+ public function follow($userId) {
+ $params = array(
+ 'http' => array(
+ 'method' => 'POST',
+ 'content' => array(),
+ 'header' => 'Content-type: application/x-www-form-urlencoded',
+ )
+ );
+ $ctx = stream_context_create($params);
+ $fp = fopen($this->getUrlFollow($userId), 'rb', false, $ctx);
+ if (!$fp) {
+ return false;
+ }
+ $response = stream_get_contents($fp);
+ if ($response === false) {
+ return false;
+ }
+ $response = json_decode($response);
+ return $response;
+ }
+
+ /**
+ * fetches DMs for a user
+ */
+ public function getDMs($sinceId=null, $count=20, $page=1) {
+ return json_decode(file_get_contents($this->getUrlDMs($sinceId, $count, $page)));
+ }
+
+ /**
+ * Send DM
+ */
+ public function sendDM($screenName, $text) {
+ $data = http_build_query(array('screen_name'=>$screenName, 'text'=>$text));
+ $params = array(
+ 'http' => array(
+ 'method' => 'POST',
+ 'content' => $data,
+ 'header' => 'Content-type: application/x-www-form-urlencoded',
+ )
+ );
+ $ctx = stream_context_create($params);
+ $fp = fopen($this->getUrlSendDM(), 'rb', false, $ctx);
+ if (!$fp) {
+ return false;
+ }
+ $response = stream_get_contents($fp);
+ if ($response === false) {
+ return false;
+ }
+ $response = json_decode($response);
+ return $response;
+ }
+
+ /**
+ * Sends a tweet
+ *
+ * @param string $txt the tweet text to send
+ * @return string URL of tweet (or false on failure)
+ */
+ public function sendTweet($txt, $limit=true) {
+ if ($limit) {
+ $txt = substr($txt, 0, 140); // twitter message size limit
+ }
+ $data = 'status=' . urlencode($txt);
+ $params = array(
+ 'http' => array(
+ 'method' => 'POST',
+ 'content' => $data,
+ 'header' => 'Content-type: application/x-www-form-urlencoded',
+ )
+ );
+ $ctx = stream_context_create($params);
+ $fp = fopen($this->getUrlTweetPost(), 'rb', false, $ctx);
+ if (!$fp) {
+ return false;
+ }
+ $response = stream_get_contents($fp);
+ if ($response === false) {
+ return false;
+ }
+ $response = json_decode($response);
+ return $response;
+ }
+
+ /**
+ * Returns the base API URL
+ */
+ protected function getUrlApi() {
+ return $this->baseUrlFull;
+ }
+
+ /**
+ * Returns the status URL
+ *
+ * @param int $num the tweet number
+ */
+ protected function getUrlStatus($num) {
+ return $this->getUrlApi() . 'statuses/show/'. urlencode($num) .'.json';
+ }
+
+ /**
+ * Returns the user timeline URL
+ */
+ protected function getUrlUserTimeline($user) {
+ return $this->getUrlApi() . 'statuses/user_timeline/'. urlencode($user) .'.json';
+ }
+
+ /**
+ * Returns the tweet posting URL
+ */
+ protected function getUrlTweetPost() {
+ return $this->getUrlApi() . 'statuses/update.json';
+ }
+
+ /**
+ * Output URL: status
+ */
+ public function getUrlOutputStatus(StdClass $tweet) {
+ return $this->baseUrl . urlencode($tweet->user->screen_name) . '/statuses/' . urlencode($tweet->id);
+ }
+
+ /**
+ * Return mentions URL
+ */
+ public function getUrlMentions($sinceId=null, $count=20) {
+ $url = $this->baseUrlFull . 'statuses/mentions.json?count=' . urlencode($count);
+ if ($sinceId !== null) {
+ $url .= '&since_id=' . urlencode($sinceId);
+ }
+ return $url;
+ }
+
+ /**
+ * Returns the followers URL
+ */
+ public function getUrlFollowers($cursor=-1) {
+ return $this->baseUrlFull . 'statuses/followers.json?cursor=' . ((int)$cursor);
+ }
+
+ /**
+ * Returns the follow-user URL
+ */
+ public function getUrlFollow($userid) {
+ return $this->baseUrlFull . 'friendships/create/' . ((int) $userid) . '.json';
+ }
+
+ /**
+ * Returns the get DMs URL
+ */
+ public function getUrlDMs($sinceId=null, $count=20, $page=1) {
+ $url = $this->baseUrlFull . 'direct_messages.json?';
+ if ($sinceId !== null) {
+ $url .= 'since_id=' . urlencode($sinceId);
+ }
+ $url .= "&page={$page}";
+ $url .= "&count={$count}";
+ return $url;
+ }
+
+ /**
+ * Returns the send DM URL
+ */
+ public function getURLSendDM() {
+ return $this->baseUrlFull . 'direct_messages/new.json';
+ }
+}
--- /dev/null
+<?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_Url
+ * @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_Url
+ */
+
+/**
+ * Monitors incoming messages for instances of URLs and responds with messages
+ * containing relevant information about detected URLs.
+ *
+ * Has an utility method accessible via
+ * $this->getPlugin('Url')->getTitle('http://foo..').
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Url
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Url
+ */
+class Phergie_Plugin_Url extends Phergie_Plugin_Abstract
+{
+ /**
+ * Links output format
+ *
+ * Can use the variables %nick%, %title% and %link% in it to display
+ * page titles and links
+ *
+ * @var string
+ */
+ protected $baseFormat = '%nick%: %message%';
+ protected $messageFormat = '[ %link% ] %title%';
+
+ /**
+ * Flag indicating whether a single response should be sent for a single
+ * message containing multiple links
+ *
+ * @var bool
+ */
+ protected $mergeLinks = true;
+
+ /**
+ * Max length of the fetched URL title
+ *
+ * @var int
+ */
+ protected $titleLength = 40;
+
+ /**
+ * Url cache to prevent spamming, especially with multiple bots on the
+ * same channel
+ *
+ * @var array
+ */
+ protected $urlCache = array();
+ protected $shortCache = array();
+
+ /**
+ * Time in seconds to store the cached entries
+ *
+ * Setting it to 0 or below disables the cache expiration
+ *
+ * @var int
+ */
+ protected $expire = 1800;
+
+ /**
+ * Number of entries to keep in the cache at one time per channel
+ *
+ * Setting it to 0 or below disables the cache limit
+ *
+ * @var int
+ */
+ protected $limit = 10;
+
+ /**
+ * Flag that determines if the plugin will fall back to using an HTTP
+ * stream when a URL using SSL is detected and OpenSSL support isn't
+ * available in the PHP installation in use
+ *
+ * @var bool
+ */
+ protected $sslFallback = true;
+
+ /**
+ * Flag that is set to true by the custom error handler if an HTTP error
+ * code has been received
+ *
+ * @var boolean
+ */
+ protected $errorStatus = false;
+ protected $errorMessage = null;
+
+ /**
+ * Flag indicating whether or not to display error messages as the title
+ * if a link posted encounters an error
+ *
+ * @var boolean
+ */
+ protected $showErrors = true;
+
+ /**
+ * Flag indicating whether to detect schemeless URLS (i.e. "example.com")
+ *
+ * @var boolean
+ */
+ protected $detectSchemeless = false;
+
+ /**
+ * List of error messages to return when the requested URL returns an
+ * HTTP error
+ *
+ * @var array
+ */
+ protected $httpErrors = array(
+ 100 => '100 Continue',
+ 200 => '200 OK',
+ 201 => '201 Created',
+ 204 => '204 No Content',
+ 206 => '206 Partial Content',
+ 300 => '300 Multiple Choices',
+ 301 => '301 Moved Permanently',
+ 302 => '302 Found',
+ 303 => '303 See Other',
+ 304 => '304 Not Modified',
+ 307 => '307 Temporary Redirect',
+ 400 => '400 Bad Request',
+ 401 => '401 Unauthorized',
+ 403 => '403 Forbidden',
+ 404 => '404 Not Found',
+ 405 => '405 Method Not Allowed',
+ 406 => '406 Not Acceptable',
+ 408 => '408 Request Timeout',
+ 410 => '410 Gone',
+ 413 => '413 Request Entity Too Large',
+ 414 => '414 Request URI Too Long',
+ 415 => '415 Unsupported Media Type',
+ 416 => '416 Requested Range Not Satisfiable',
+ 417 => '417 Expectation Failed',
+ 500 => '500 Internal Server Error',
+ 501 => '501 Method Not Implemented',
+ 503 => '503 Service Unavailable',
+ 506 => '506 Variant Also Negotiates'
+ );
+
+ /**
+ * An array containing a list of TLDs used for non-scheme matches
+ *
+ * @var array
+ */
+ protected $tldList = array();
+
+ /**
+ * Shortener object
+ */
+ protected $shortener;
+
+ /**
+ * Array of renderers
+ */
+ protected $renderers = array();
+
+ /**
+ * Initializes settings, checks dependencies.
+ *
+ * @return void
+ */
+ public function onConnect()
+ {
+ // make the shortener configurable
+ $shortener = $this->getConfig('url.shortener', 'Trim');
+ $shortener = "Phergie_Plugin_Url_Shorten_{$shortener}";
+ $this->shortener = new $shortener;
+
+ if (!$this->shortener instanceof Phergie_Plugin_Url_Shorten_Abstract) {
+ $this->fail("Declared shortener class {$shortener} is not of proper ancestry");
+ }
+
+ // Get a list of valid TLDs
+ if (!is_array($this->tldList) || count($this->tldList) <= 6) {
+ /* Omitted for port
+ if ($this->pluginLoaded('Tld')) {
+ $this->tldList = Phergie_Plugin_Tld::getTlds();
+ if (is_array($this->tldList)) {
+ $this->tldList = array_keys($this->tldList);
+ }
+ }
+ */
+ if (!is_array($this->tldList) || count($this->tldList) <= 0) {
+ $this->tldList = array('ac', 'ad', 'ae', 'aero', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', 'aq', 'ar', 'arpa', 'as', 'asia', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh', 'bi', 'biz', 'bj', 'bl', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cat', 'cc', 'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'com', 'coop', 'cr', 'cu', 'cv', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 'ec', 'edu', 'ee', 'eg', 'eh', 'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge', 'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gov', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw', 'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'info', 'int', 'io', 'iq', 'ir', 'is', 'it', 'je', 'jm', 'jo', 'jobs', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp', 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu', 'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mf', 'mg', 'mh', 'mil', 'mk', 'ml', 'mm', 'mn', 'mo', 'mobi', 'mp', 'mq', 'mr', 'ms', 'mt', 'mu', 'museum', 'mv', 'mw', 'mx', 'my', 'mz', 'na', 'name', 'nc', 'ne', 'net', 'nf', 'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'org', 'pa', 'pe', 'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr', 'pro', 'ps', 'pt', 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', 'rw', 'sa', 'sb', 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn', 'so', 'sr', 'st', 'su', 'sv', 'sy', 'sz', 'tc', 'td', 'tel', 'tf', 'tg', 'th', 'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'travel', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug', 'uk', 'um', 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye', 'yt', 'yu', 'za', 'zm', 'zw');
+ }
+ rsort($this->tldList);
+ }
+
+ // load config (a bit ugly, but focusing on porting):
+ foreach (
+ array(
+ 'detect_schemeless' => 'detectSchemeless',
+ 'base_format' => 'baseFormat',
+ 'message_format' => 'messageFormat',
+ 'merge_links' => 'mergeLinks',
+ 'title_length' => 'titleLength',
+ 'show_errors' => 'showErrors',
+ ) as $config => $local) {
+ if (isset($this->config["url.{$config}"])) {
+ $this->$local = $this->config["uri.{$config}"];
+ }
+ }
+ }
+
+ /**
+ * Checks an incoming message for the presence of a URL and, if one is
+ * found, responds with its title if it is an HTML document and the
+ * shortened equivalent of its original URL if it meets length requirements.
+ *
+ * @todo Update this to pull configuration settings from $this->config
+ * rather than caching them as class properties
+ * @return void
+ */
+ public function onPrivmsg()
+ {
+ $source = $this->getEvent()->getSource();
+ $user = $this->getEvent()->getNick();
+
+ $pattern = '#'.($this->detectSchemeless ? '' : 'https?://').'(?:([0-9]{1,3}(?:\.[0-9]{1,3}){3})(?![^/]) | ('
+ .($this->detectSchemeless ? '(?<!http:/|https:/)[@/\\\]' : '').')?(?:(?:[a-z0-9_-]+\.?)+\.[a-z0-9]{1,6}))[^\s]*#xis';
+
+ // URL Match
+ if (preg_match_all($pattern, $this->getEvent()->getArgument(1), $matches, PREG_SET_ORDER)) {
+ $responses = array();
+ foreach ($matches as $m) {
+ $url = trim(rtrim($m[0], ', ].?!;'));
+
+ // Check to see if the URL was from an email address, is a directory, etc
+ if (!empty($m[2])) {
+ $this->debug('Invalid Url: URL is either an email or a directory path. (' . $url . ')');
+ continue;
+ }
+
+ // Parse the given URL
+ if (!$parsed = $this->parseUrl($url)) {
+ $this->debug('Invalid Url: Could not parse the URL. (' . $url . ')');
+ continue;
+ }
+
+ // allow out-of-class renderers to handle this URL
+ foreach ($this->renderers as $renderer) {
+ if ($renderer->renderUrl($parsed) === true) {
+ // renderers should return true if they've fully
+ // rendered the passed URL (they're responsible
+ // for their own output)
+ $this->debug('Handled by renderer: ' . get_class($renderer));
+ continue 2;
+ }
+ }
+
+ // Check to see if the given IP/Host is valid
+ if (!empty($m[1]) and !$this->checkValidIP($m[1])) {
+ $this->debug('Invalid Url: ' . $m[1] . ' is not a valid IP address. (' . $url . ')');
+ continue;
+ }
+
+ // Process TLD if it's not an IP
+ if (empty($m[1])) {
+ // Get the TLD from the host
+ $pos = strrpos($parsed['host'], '.');
+ $parsed['tld'] = ($pos !== false ? substr($parsed['host'], ($pos+1)) : '');
+
+ // Check to see if the URL has a valid TLD
+ if (is_array($this->tldList) && !in_array(strtolower($parsed['tld']), $this->tldList)) {
+ $this->debug('Invalid Url: ' . $parsed['tld'] . ' is not a supported TLD. (' . $url . ')');
+ continue;
+ }
+ }
+
+ // Check to see if the URL is to a secured site or not and handle it accordingly
+ if ($parsed['scheme'] == 'https' && !extension_loaded('openssl')) {
+ if (!$this->sslFallback) {
+ $this->debug('Invalid Url: HTTPS is an invalid scheme, OpenSSL isn\'t available. (' . $url . ')');
+ continue;
+ } else {
+ $parsed['scheme'] = 'http';
+ }
+ }
+
+ if (!in_array($parsed['scheme'], array('http', 'https'))) {
+ $this->debug('Invalid Url: ' . $parsed['scheme'] . ' is not a supported scheme. (' . $url . ')');
+ continue;
+ }
+ $url = $this->glueURL($parsed);
+ unset($parsed);
+
+ // Convert url
+ $shortenedUrl = $this->shortener->shorten($url);
+
+ // Prevent spamfest
+ if ($this->checkUrlCache($url, $shortenedUrl)) {
+ $this->debug('Invalid Url: URL is in the cache. (' . $url . ')');
+ continue;
+ }
+
+ $title = self::getTitle($url);
+ if (!empty($title)) {
+ $responses[] = str_replace(
+ array(
+ '%title%',
+ '%link%',
+ '%nick%'
+ ), array(
+ $title,
+ $shortenedUrl,
+ $user
+ ), $this->messageFormat
+ );
+ }
+
+ // Update cache
+ $this->updateUrlCache($url, $shortenedUrl);
+ unset($title, $shortenedUrl, $title);
+ }
+ /**
+ * Check to see if there were any URL responses, format them and handle if they
+ * get merged into one message or not
+ */
+ if (count($responses) > 0) {
+ if ($this->mergeLinks) {
+ $message = str_replace(
+ array(
+ '%message%',
+ '%nick%'
+ ), array(
+ implode('; ', $responses),
+ $user
+ ), $this->baseFormat
+ );
+ $this->doPrivmsg($source, $message);
+ } else {
+ foreach ($responses as $response) {
+ $message = str_replace(
+ array(
+ '%message%',
+ '%nick%'
+ ), array(
+ implode('; ', $responses),
+ $user
+ ), $this->baseFormat
+ );
+ $this->doPrivmsg($source, $message);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks a given URL (+shortened) against the cache to verify if they were
+ * previously posted on the channel.
+ *
+ * @param string $url The URL to check against
+ * @param string $shortenedUrl The shortened URL to check against
+ *
+ * @return bool
+ */
+ protected function checkUrlCache($url, $shortenedUrl)
+ {
+ $source = $this->getEvent()->getSource();
+
+ /**
+ * Transform the URL (+shortened) into a HEX CRC32 checksum to prevent potential problems
+ * and minimize the size of the cache for less cache bloat.
+ */
+ $url = $this->getUrlChecksum($url);
+ $shortenedUrl = $this->getUrlChecksum($shortenedUrl);
+
+ $cache = array(
+ 'url' => isset($this->urlCache[$source][$url]) ? $this->urlCache[$source][$url] : null,
+ 'shortened' => isset($this->shortCache[$source][$shortenedUrl]) ? $this->shortCache[$source][$shortenedUrl] : null
+ );
+
+ $expire = $this->expire;
+ $this->debug("Cache expire: {$expire}");
+ /**
+ * If cache expiration is enabled, check to see if the given url has expired in the cache
+ * If expire is disabled, simply check to see if the url is listed
+ */
+ if (($expire > 0 && (($cache['url'] + $expire) > time() || ($cache['shortened'] + $expire) > time()))
+ || ($expire <= 0 && (isset($cache['url']) || isset($cache['shortened'])))
+ ) {
+ unset($cache, $url, $shortenedUrl, $expire);
+ return true;
+ }
+ unset($cache, $url, $shortenedUrl, $expire);
+ return false;
+ }
+
+ /**
+ * Updates the cache and adds the given URL (+shortened) to the cache. It
+ * also handles cleaning the cache of old entries as well.
+ *
+ * @param string $url The URL to add to the cache
+ * @param string $shortenedUrl The shortened to add to the cache
+ *
+ * @return bool
+ */
+ protected function updateUrlCache($url, $shortenedUrl)
+ {
+ $source = $this->getEvent()->getSource();
+
+ /**
+ * Transform the URL (+shortened) into a HEX CRC32 checksum to prevent potential problems
+ * and minimize the size of the cache for less cache bloat.
+ */
+ $url = $this->getUrlChecksum($url);
+ $shortenedUrl = $this->getUrlChecksum($shortenedUrl);
+ $time = time();
+
+ // Handle the URL cache and remove old entries that surpass the limit if enabled
+ $this->urlCache[$source][$url] = $time;
+ if ($this->limit > 0 && count($this->urlCache[$source]) > $this->limit) {
+ asort($this->urlCache[$source], SORT_NUMERIC);
+ array_shift($this->urlCache[$source]);
+ }
+
+ // Handle the shortened cache and remove old entries that surpass the limit if enabled
+ $this->shortCache[$source][$shortenedUrl] = $time;
+ if ($this->limit > 0 && count($this->shortCache[$source]) > $this->limit) {
+ asort($this->shortCache[$source], SORT_NUMERIC);
+ array_shift($this->shortCache[$source]);
+ }
+ unset($url, $shortenedUrl, $time);
+ }
+
+ /**
+ * Transliterates a UTF-8 string into corresponding ASCII characters and
+ * truncates and appends an ellipsis to the string if it exceeds a given
+ * length.
+ *
+ * @param string $str String to decode
+ * @param int $trim Maximum string length, optional
+ *
+ * @return string
+ */
+ protected function decode($str, $trim = null)
+ {
+ $out = $this->decodeTranslit($str);
+ if ($trim > 0) {
+ $out = substr($out, 0, $trim) . (strlen($out) > $trim ? '...' : '');
+ }
+ return $out;
+ }
+
+ /**
+ * Custom error handler meant to handle 404 errors and such
+ *
+ * @param int $errno the error code
+ * @param string $errstr the error string
+ * @param string $errfile file the error occured in
+ * @param int $errline line the error occured on
+ *
+ * @return bool
+ */
+ public function onPhpError($errno, $errstr, $errfile, $errline)
+ {
+ if ($errno === E_WARNING) {
+ // Check to see if there was HTTP warning while connecting to the site
+ if (preg_match('{HTTP/1\.[01] ([0-9]{3})}i', $errstr, $m)) {
+ $this->errorStatus = $m[1];
+ $this->errorMessage = (isset($this->httpErrors[$m[1]]) ? $this->httpErrors[$m[1]] : $m[1]);
+ $this->debug('PHP Warning: ' . $errstr . 'in ' . $errfile . ' on line ' . $errline);
+ return true;
+ }
+
+ // Safely ignore these SSL warnings so they don't appear in the log
+ if (stripos($errstr, 'SSL: fatal protocol error in') !== false
+ || stripos($errstr, 'failed to open stream') !== false
+ || stripos($errstr, 'HTTP request failed') !== false
+ || stripos($errstr, 'SSL: An existing connection was forcibly closed by the remote host') !== false
+ || stripos($errstr, 'Failed to enable crypto in') !== false
+ || stripos($errstr, 'SSL: An established connection was aborted by the software in your host machine') !== false
+ || stripos($errstr, 'SSL operation failed with code') !== false
+ || stripos($errstr, 'unable to connect to') !== false
+ ) {
+ $this->errorStatus = true;
+ $this->debug('PHP Warning: ' . $errstr . 'in ' . $errfile . ' on line ' . $errline);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Takes a url, parses and cleans the URL without of all the junk
+ * and then return the hex checksum of the url.
+ *
+ * @param string $url url to checksum
+ *
+ * @return string the hex checksum of the cleaned url
+ */
+ protected function getUrlChecksum($url)
+ {
+ $checksum = strtolower(urldecode($this->glueUrl($url, true)));
+ $checksum = preg_replace('#\s#', '', $this->decodeTranslit($checksum));
+ return dechex(crc32($checksum));
+ }
+
+ /**
+ * Parses a given URI and procceses the output to remove redundant
+ * or missing values.
+ *
+ * @param string $url the url to parse
+ *
+ * @return array the url components
+ */
+ protected function parseUrl($url)
+ {
+ if (is_array($url)) return $url;
+
+ $url = trim(ltrim($url, ' /@\\'));
+ if (!preg_match('&^(?:([a-z][-+.a-z0-9]*):)&xis', $url, $matches)) {
+ $url = 'http://' . $url;
+ }
+ $parsed = parse_url($url);
+
+ if (!isset($parsed['scheme'])) {
+ $parsed['scheme'] = 'http';
+ }
+ $parsed['scheme'] = strtolower($parsed['scheme']);
+
+ if (isset($parsed['path']) && !isset($parsed['host'])) {
+ $host = $parsed['path'];
+ $path = '';
+ if (strpos($parsed['path'], '/') !== false) {
+ list($host, $path) = array_pad(explode('/', $parsed['path'], 2), 2, null);
+ }
+ $parsed['host'] = $host;
+ $parsed['path'] = $path;
+ }
+
+ return $parsed;
+ }
+
+ /**
+ * Parses a given URI and then glues it back together in the proper format.
+ * If base is set, then it chops off the scheme, user and pass and fragment
+ * information to return a more unique base URI.
+ *
+ * @param string $uri uri to rebuild
+ * @param string $base set to true to only return the base components
+ *
+ * @return string the rebuilt uri
+ */
+ protected function glueUrl($uri, $base = false)
+ {
+ $parsed = $uri;
+ if (!is_array($parsed)) {
+ $parsed = $this->parseUrl($parsed);
+ }
+
+ if (is_array($parsed)) {
+ $uri = '';
+ if (!$base) {
+ $uri .= (!empty($parsed['scheme']) ? $parsed['scheme'] . ':' .
+ ((strtolower($parsed['scheme']) == 'mailto') ? '' : '//') : '');
+ $uri .= (!empty($parsed['user']) ? $parsed['user'] .
+ (!empty($parsed['pass']) ? ':' . $parsed['pass'] : '') . '@' : '');
+ }
+ if ($base && !empty($parsed['host'])) {
+ $parsed['host'] = trim($parsed['host']);
+ if (substr($parsed['host'], 0, 4) == 'www.') {
+ $parsed['host'] = substr($parsed['host'], 4);
+ }
+ }
+ $uri .= (!empty($parsed['host']) ? $parsed['host'] : '');
+ if (!empty($parsed['port'])
+ && (($parsed['scheme'] == 'http' && $parsed['port'] == 80)
+ || ($parsed['scheme'] == 'https' && $parsed['port'] == 443))
+ ) {
+ unset($parsed['port']);
+ }
+ $uri .= (!empty($parsed['port']) ? ':' . $parsed['port'] : '');
+ if (!empty($parsed['path']) && (!$base || $base && $parsed['path'] != '/')) {
+ $uri .= (substr($parsed['path'], 0, 1) == '/') ? $parsed['path'] : ('/' . $parsed['path']);
+ }
+ $uri .= (!empty($parsed['query']) ? '?' . $parsed['query'] : '');
+ if (!$base) {
+ $uri .= (!empty($parsed['fragment']) ? '#' . $parsed['fragment'] : '');
+ }
+ }
+ return $uri;
+ }
+
+ /**
+ * Checks the given string to see if its a valid IP4 address
+ *
+ * @param string $ip the ip to validate
+ *
+ * @return bool
+ */
+ protected function checkValidIP($ip)
+ {
+ return long2ip(ip2long($ip)) === $ip;
+ }
+
+ /**
+ * Returns the title of the given page
+ *
+ * @param string $url url to the page
+ *
+ * @return string title
+ */
+ public function getTitle($url)
+ {
+ $opts = array(
+ 'http' => array(
+ 'timeout' => 3.5,
+ 'method' => 'GET',
+ 'user_agent' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.12) Gecko/20080201 Firefox/2.0.0.12'
+ )
+ );
+ $context = stream_context_create($opts);
+
+ if ($page = fopen($url, 'r', false, $context)) {
+ stream_set_timeout($page, 3.5);
+ $data = stream_get_meta_data($page);
+ foreach ($data['wrapper_data'] as $header) {
+ if (preg_match('/^Content-Type: ([^;]+)/', $header, $match)
+ && !preg_match('#^(text/x?html|application/xhtml+xml)$#', $match[1])
+ ) {
+ $title = $match[1];
+ }
+ }
+ if (!isset($title)) {
+ $content = '';
+ $tstamp = time() + 5;
+
+ while ($chunk = fread($page, 64)) {
+ $data = stream_get_meta_data($page);
+ if ($data['timed_out']) {
+ $this->debug('Url Timed Out: ' . $url);
+ $this->errorStatus = true;
+ break;
+ }
+ $content .= $chunk;
+ // Check for timeout
+ if (time() > $tstamp) break;
+ // Try to read title
+ if (preg_match('#<title[^>]*>(.*)#is', $content, $m)) {
+ // Start another loop to grab some more data in order to be sure we have the complete title
+ $content = $m[1];
+ $loop = 2;
+ while (($chunk = fread($page, 64)) && $loop-- && !strstr($content, '<')) {
+ $content .= $chunk;
+ // Check for timeout
+ if (time() > $tstamp) break;
+ }
+ preg_match('#^([^<]*)#is', $content, $m);
+ $title = preg_replace('#\s+#', ' ', $m[1]);
+ $title = trim($this->decode($title, $this->titleLength));
+ break;
+ }
+ // Title won't appear beyond that point so stop parsing
+ if (preg_match('#</head>|<body#i', $content)) {
+ break;
+ }
+ }
+ }
+ fclose($page);
+ } else if (!$this->errorStatus) {
+ $this->debug('Couldn\t Open Url: ' . $url);
+ }
+
+ if (empty($title)) {
+ if ($this->errorStatus) {
+ if (!$this->showErrors || empty($this->errorMessage)) {
+ return;
+ }
+ $title = $this->errorMessage;
+ $this->errorStatus = false;
+ $this->errorMessage = null;
+ } else {
+ $title = 'No Title';
+ }
+ }
+
+ return $title;
+ }
+
+ /**
+ * Output a debug message
+ *
+ * @param string $msg the message to output
+ *
+ * @return void
+ */
+ protected function debug($msg)
+ {
+ echo "(DEBUG:Url) $msg\n";
+ }
+
+ /**
+ * Placeholder/porting helper. Has no function.
+ *
+ * @param string $str a string to return
+ *
+ * @return string
+ */
+ protected function decodeTranslit($str)
+ {
+ // placeholder/porting helper
+ return $str;
+ }
+
+ /**
+ * Add a renderer to the stack
+ *
+ * @param object $obj the renderer to add
+ *
+ * @return void
+ */
+ public function registerRenderer($obj)
+ {
+ $this->renderers[] = $obj;
+ array_unique($this->renderers);
+ }
+}
--- /dev/null
+<?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_Php
+ * @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_Php
+ */
+
+/**
+ * URL shortener abstract class
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Url
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Url
+ */
+abstract class Phergie_Plugin_Url_Shorten_Abstract
+{
+ /**
+ * Takes a long url and returns a shortened link
+ *
+ * @param string $url the url to shorten
+ *
+ * @return string string the shortened url
+ */
+ public abstract function shorten($url);
+}
--- /dev/null
+<?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_Php
+ * @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_Php
+ */
+
+/**
+ * Shortens urls via the tr.im service
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_Url
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_Url
+ */
+class Phergie_Plugin_Url_Shorten_Trim extends Phergie_Plugin_Url_Shorten_Abstract
+{
+ /**
+ * Short a URL through the tr.im api
+ *
+ * @param string $url the url to shorten
+ *
+ * @return string string the shortened url
+ */
+ public function shorten($url)
+ {
+ return file_get_contents('http://api.tr.im/v1/trim_simple?url=' . rawurlencode($url));
+ }
+}
--- /dev/null
+<?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_UserInfo
+ * @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_UserInfo
+ */
+
+/**
+ * Provides an API for querying information on users.
+ *
+ * @category Phergie
+ * @package Phergie_Plugin_UserInfo
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Plugin_UserInfo
+ */
+class Phergie_Plugin_UserInfo extends Phergie_Plugin_Abstract
+{
+ const REGULAR = 1;
+ const VOICE = 2;
+ const HALFOP = 4;
+ const OP = 8;
+ const ADMIN = 16;
+ const OWNER = 32;
+
+ /**
+ * An array containing all the user information for a given channel
+ *
+ * @var array
+ */
+ protected $store = array();
+
+ /**
+ * Tracks mode changes
+ *
+ * @return void
+ */
+ public function onMode()
+ {
+ $args = $this->event->getArguments();
+
+ if (count($args) != 3) {
+ return;
+ }
+
+ list($chan, $modes, $nicks) = $args;
+
+ if (!preg_match('/(?:\+|-)[hovaq+-]+/i', $modes)) {
+ return;
+ }
+
+ $chan = trim(strtolower($chan));
+ $modes = str_split(trim(strtolower($modes)), 1);
+ $nicks = explode(' ', trim(strtolower($nicks)));
+ $operation = array_shift($modes); // + or -
+
+ while ($char = array_shift($modes)) {
+ $nick = array_shift($nicks);
+ $mode = null;
+
+ switch ($char) {
+ case 'q':
+ $mode = self::OWNER;
+ break;
+ case 'a':
+ $mode = self::ADMIN;
+ break;
+ case 'o':
+ $mode = self::OP;
+ break;
+ case 'h':
+ $mode = self::HALFOP;
+ break;
+ case 'v':
+ $mode = self::VOICE;
+ break;
+ }
+
+ if (!empty($mode)) {
+ if ($operation == '+') {
+ $this->store[$chan][$nick] |= $mode;
+ } else if ($operation == '-') {
+ $this->store[$chan][$nick] ^= $mode;
+ }
+ }
+ }
+ }
+
+ /**
+ * Tracks users joining a channel
+ *
+ * @return void
+ */
+ public function onJoin()
+ {
+ $chan = trim(strtolower($this->event->getArgument(0)));
+ $nick = trim(strtolower($this->event->getNick()));
+
+ $this->store[$chan][$nick] = self::REGULAR;
+ }
+
+ /**
+ * Tracks users leaving a channel
+ *
+ * @return void
+ */
+ public function onPart()
+ {
+ $chan = trim(strtolower($this->event->getArgument(0)));
+ $nick = trim(strtolower($this->event->getNick()));
+
+ if (isset($this->store[$chan][$nick])) {
+ unset($this->store[$chan][$nick]);
+ }
+ }
+
+ /**
+ * Tracks users quitting a server
+ *
+ * @return void
+ */
+ public function onQuit()
+ {
+ $nick = trim(strtolower($this->event->getNick()));
+
+ foreach ($this->store as $chan => $store) {
+ if (isset($store[$nick])) {
+ unset($this->store[$chan][$nick]);
+ }
+ }
+ }
+
+ /**
+ * Tracks users changing nicks
+ *
+ * @return void
+ */
+ public function onNick()
+ {
+ $nick = trim(strtolower($this->event->getNick()));
+ $newNick = trim(strtolower($this->event->getArgument(0)));
+
+ foreach ($this->store as $chan => $store) {
+ if (isset($store[$nick])) {
+ $this->store[$chan][$newNick] = $store[$nick];
+ unset($this->store[$chan][$nick]);
+ }
+ }
+ }
+
+ /**
+ * Populates the internal user listing for a channel when the bot joins it.
+ *
+ * @return void
+ */
+ public function onResponse()
+ {
+ if ($this->event->getCode() != Phergie_Event_Response::RPL_NAMREPLY) {
+ return;
+ }
+
+ $desc = preg_split('/[@*=]\s*/', $this->event->getDescription(), 2);
+ list($chan, $users) = array_pad(explode(' :', trim($desc[1])), 2, null);
+ $users = explode(' ', trim($users));
+
+ $chan = trim(strtolower($chan));
+
+ foreach ($users as $user) {
+ if (empty($user)) {
+ continue;
+ }
+
+ $user = trim(strtolower($user));
+ $flag = self::REGULAR;
+
+ if ($user[0] == '~') {
+ $flag |= self::OWNER;
+ } else if ($user[0] == '&') {
+ $flag |= self::ADMIN;
+ } else if ($user[0] == '@') {
+ $flag |= self::OP;
+ } else if ($user[0] == '%') {
+ $flag |= self::HALFOP;
+ } else if ($user[0] == '+') {
+ $flag |= self::VOICE;
+ }
+
+ if ($flag != self::REGULAR) {
+ $user = substr($user, 1);
+ }
+
+ $this->store[$chan][$user] = $flag;
+ }
+ }
+
+ /**
+ * Debugging function
+ *
+ * @return void
+ */
+ public function onPrivmsg()
+ {
+ if ($this->getConfig('debug', false) == false) {
+ return;
+ }
+
+ list($target, $msg) = array_pad($this->event->getArguments(), 2, null);
+
+ if (preg_match('#^ishere (\S+)$#', $msg, $m)) {
+ $this->doPrivmsg($target, $this->isIn($m[1], $target) ? 'true' : 'false');
+ } elseif (preg_match('#^isowner (\S+)$#', $msg, $m)) {
+ $this->doPrivmsg($target, $this->isOwner($m[1], $target) ? 'true' : 'false');
+ } elseif (preg_match('#^isadmin (\S+)$#', $msg, $m)) {
+ $this->doPrivmsg($target, $this->isAdmin($m[1], $target) ? 'true' : 'false');
+ } elseif (preg_match('#^isop (\S+)$#', $msg, $m)) {
+ $this->doPrivmsg($target, $this->isOp($m[1], $target) ? 'true' : 'false');
+ } elseif (preg_match('#^ishop (\S+)$#', $msg, $m)) {
+ $this->doPrivmsg($target, $this->isHalfop($m[1], $target) ? 'true' : 'false');
+ } elseif (preg_match('#^isvoice (\S+)$#', $msg, $m)) {
+ $this->doPrivmsg($target, $this->isVoice($m[1], $target) ? 'true' : 'false');
+ } elseif (preg_match('#^channels (\S+)$#', $msg, $m)) {
+ $channels = $this->getChannels($m[1]);
+ $this->doPrivmsg($target, $channels ? join(', ', $channels) : 'unable to find nick');
+ } elseif (preg_match('#^users (\S+)$#', $msg, $m)) {
+ $nicks = $this->getUsers($m[1]);
+ $this->doPrivmsg($target, $nicks ? join(', ', $nicks) : 'unable to find channel');
+ } elseif (preg_match('#^random (\S+)$#', $msg, $m)) {
+ $nick = $this->getrandomuser($m[1]);
+ $this->doPrivmsg($target, $nick ? $nick : 'unable to find channel');
+ }
+ }
+
+ /**
+ * Checks whether or not a given user has a mode
+ *
+ * @param int $mode A numeric mode (identified by the class constants)
+ * @param string $nick The nick to check
+ * @param string $chan The channel to check in
+ *
+ * @return bool
+ */
+ public function is($mode, $nick, $chan)
+ {
+ $chan = trim(strtolower($chan));
+ $nick = trim(strtolower($nick));
+
+ if (!isset($this->store[$chan][$nick])) {
+ return false;
+ }
+
+ return ($this->store[$chan][$nick] & $mode) != 0;
+ }
+
+ /**
+ * Checks whether or not a given user has owner (~) status
+ *
+ * @param string $nick The nick to check
+ * @param string $chan The channel to check in
+ *
+ * @return bool
+ */
+ public function isOwner($nick, $chan)
+ {
+ return $this->is(self::OWNER, $nick, $chan);
+ }
+
+ /**
+ * Checks whether or not a given user has admin (&) status
+ *
+ * @param string $nick The nick to check
+ * @param string $chan The channel to check in
+ *
+ * @return bool
+ */
+ public function isAdmin($nick, $chan)
+ {
+ return $this->is(self::ADMIN, $nick, $chan);
+ }
+
+ /**
+ * Checks whether or not a given user has operator (@) status
+ *
+ * @param string $nick The nick to check
+ * @param string $chan The channel to check in
+ *
+ * @return bool
+ */
+ public function isOp($nick, $chan)
+ {
+ return $this->is(self::OP, $nick, $chan);
+ }
+
+ /**
+ * Checks whether or not a given user has halfop (%) status
+ *
+ * @param string $nick The nick to check
+ * @param string $chan The channel to check in
+ *
+ * @return bool
+ */
+ public function isHalfop($nick, $chan)
+ {
+ return $this->is(self::HALFOP, $nick, $chan);
+ }
+
+ /**
+ * Checks whether or not a given user has voice (+) status
+ *
+ * @param string $nick The nick to check
+ * @param string $chan The channel to check in
+ *
+ * @return bool
+ */
+ public function isVoice($nick, $chan)
+ {
+ return $this->is(self::VOICE, $nick, $chan);
+ }
+
+ /**
+ * Checks whether or not a given user is in a channel
+ *
+ * @param string $nick The nick to check
+ * @param string $chan The channel to check in
+ *
+ * @return bool
+ */
+ public function isIn($nick, $chan)
+ {
+ return $this->is(self::REGULAR, $nick, $chan);
+ }
+
+ /**
+ * Returns the entire user list for a channel or false if the bot is not
+ * in the channel.
+ *
+ * @param string $chan The channel name
+ *
+ * @return array|bool
+ */
+ public function getUsers($chan)
+ {
+ $chan = trim(strtolower($chan));
+ if (isset($this->store[$chan])) {
+ return array_keys($this->store[$chan]);
+ }
+ return false;
+ }
+
+ /**
+ * Returns the nick of a random user present in a given channel or false
+ * if the bot is not present in the channel.
+ *
+ * @param string $chan The channel name
+ *
+ * @return array|bool
+ */
+ public function getRandomUser($chan)
+ {
+ $chan = trim(strtolower($chan));
+
+ if (isset($this->store[$chan])) {
+ $ignore = array('chanserv', 'q', 'l', 's');
+
+ do {
+ $nick = array_rand($this->store[$chan], 1);
+ } while (in_array($nick, $ignore));
+
+ return $nick;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a list of channels in which a given user is present.
+ *
+ * @param string $nick Nick of the user (optional, defaults to the bot's
+ * nick)
+ *
+ * @return array|bool
+ */
+ public function getChannels($nick = null)
+ {
+ if (empty($nick)) {
+ $nick = $this->connection->getNick();
+ }
+
+ $nick = trim(strtolower($nick));
+ $channels = array();
+
+ foreach ($this->store as $chan => $store) {
+ if (isset($store[$nick])) {
+ $channels[] = $chan;
+ }
+ }
+
+ return $channels;
+ }
+}
--- /dev/null
+<?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 obtaining and processing incoming 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_Process_Abstract
+{
+ /**
+ * Current driver instance
+ *
+ * @var Phergie_Driver_Abstract
+ */
+ protected $driver;
+
+ /**
+ * Current connection handler instance
+ *
+ * @var Phergie_Connection_Handler
+ */
+ protected $connections;
+
+ /**
+ * Current plugin handler instance
+ *
+ * @var Phergie_Plugin_Handler
+ */
+ protected $plugins;
+
+ /**
+ * Current event handler instance
+ *
+ * @var Phergie_Event_Handler
+ */
+ protected $events;
+
+ /**
+ * Current end-user interface instance
+ *
+ * @var Phergie_Ui_Abstract
+ */
+ protected $ui;
+
+ /**
+ * List of arguments for use within the instance
+ *
+ * @var array
+ */
+ protected $options = array();
+
+ /**
+ * Gets the required class refences from Phergie_Bot.
+ *
+ * @param Phergie_Bot $bot Current bot instance in use
+ * @param array $options Optional processor arguments
+ *
+ * @return void
+ */
+ public function __construct(Phergie_Bot $bot, array $options = array())
+ {
+ $this->driver = $bot->getDriver();
+ $this->plugins = $bot->getPluginHandler();
+ $this->connections = $bot->getConnectionHandler();
+ $this->events = $bot->getEventHandler();
+ $this->ui = $bot->getUi();
+ $this->options = $options;
+ }
+
+ /**
+ * Sends resulting outgoing events from ealier processing in handleEvents.
+ *
+ * @param Phergie_Connection $connection Active connection
+ *
+ * @return void
+ */
+ protected function processEvents(Phergie_Connection $connection)
+ {
+ if (count($this->events)) {
+ $this->plugins->preDispatch();
+ foreach ($this->events as $event) {
+ $this->ui->onCommand($event, $connection);
+
+ $method = 'do' . ucfirst(strtolower($event->getType()));
+ call_user_func_array(
+ array($this->driver, $method),
+ $event->getArguments()
+ );
+ }
+ $this->plugins->postDispatch();
+
+ if ($this->events->hasEventOfType(Phergie_Event_Request::TYPE_QUIT)) {
+ $this->ui->onQuit($connection);
+ $this->connections->removeConnection($connection);
+ }
+
+ $this->events->clearEvents();
+ }
+ }
+
+ /**
+ * Obtains and processes incoming events.
+ *
+ * @return void
+ */
+ public abstract function handleEvents();
+}
--- /dev/null
+<?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
+ */
+
+/**
+ * Connection data processor which polls to handle input in an
+ * asynchronous manner. Will also cause the application tick at
+ * the user-defined wait time.
+ *
+ * @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_Process_Async extends Phergie_Process_Abstract
+{
+ /**
+ * Length of time to poll for stream activity (seconds)
+ *
+ * @var int
+ */
+ protected $sec;
+
+ /**
+ * Length of time to poll for stream activity (microseconds)
+ *
+ * @var int
+ */
+ protected $usec;
+
+ /**
+ * Length of time to wait between ticks.
+ *
+ * @var int
+ */
+ protected $wait = 0;
+
+ /**
+ * Records when the application last performed a tick
+ *
+ * @var int
+ */
+ protected $lastTick = 0;
+
+ /**
+ * Overrides the parent class to set the poll time.
+ *
+ * @param Phergie_Bot $bot Main bot class
+ * @param array $options Processor arguments
+ *
+ * @return void
+ */
+ public function __construct(Phergie_Bot $bot, array $options)
+ {
+ if (!$bot->getDriver() instanceof Phergie_Driver_Streams) {
+ throw new Phergie_Process_Exception(
+ 'The Async event processor requires the Streams driver'
+ );
+ }
+
+ foreach (array('sec', 'usec') as $var) {
+ if (isset($options[$var])) {
+ if (!is_int($options[$var])) {
+ throw new Phergie_Process_Exception(
+ 'Processor option "' . $var . '" must be an integer'
+ );
+ }
+ $this->$var = $options[$var];
+ }
+ }
+
+ if (empty($this->sec) && empty($this->usec)) {
+ throw new Phergie_Process_Exception(
+ 'One of the processor options "sec" or "usec" must be specified'
+ );
+ }
+
+ parent::__construct($bot, $options);
+ }
+
+ /**
+ * Waits for stream activity and performs event processing on
+ * connections with data to read.
+ *
+ * @return void
+ */
+ protected function handleEventsAsync()
+ {
+ $hostmasks = $this->driver->getActiveReadSockets($this->sec, $this->usec);
+ if (!$hostmasks) {
+ return;
+ }
+ $connections = $this->connections->getConnections($hostmasks);
+ foreach ($connections as $connection) {
+ $this->driver->setConnection($connection);
+ $this->plugins->setConnection($connection);
+ $this->plugins->onTick();
+
+ if ($event = $this->driver->getEvent()) {
+ $this->ui->onEvent($event, $connection);
+ $this->plugins->setEvent($event);
+
+ if (!$this->plugins->preEvent()) {
+ continue;
+ }
+
+ $this->plugins->{'on' . ucfirst($event->getType())}();
+ }
+
+ $this->processEvents($connection);
+ }
+ }
+
+ /**
+ * Perform application tick event on all plugins and connections.
+ *
+ * @return void
+ */
+ protected function doTick()
+ {
+ foreach ($this->connections as $connection) {
+ $this->plugins->setConnection($connection);
+ $this->plugins->onTick();
+ $this->processEvents($connection);
+ }
+ }
+
+ /**
+ * Obtains and processes incoming events, then sends resulting outgoing
+ * events.
+ *
+ * @return void
+ */
+ public function handleEvents()
+ {
+ $time = time();
+ if ($this->lastTick == 0 || ($this->lastTick + $this->wait <= $time)) {
+ $this->doTick();
+ $this->lastTick = $time;
+ }
+ $this->handleEventsAsync();
+ }
+}
--- /dev/null
+<?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 event processor 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_Process_Exception extends Phergie_Exception
+{
+}
--- /dev/null
+<?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
+ */
+
+/**
+ * Connection data processor which reads all connections looking
+ * for a response.
+ *
+ * @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_Process_Standard extends Phergie_Process_Abstract
+{
+ /**
+ * Obtains and processes incoming events, then sends resulting outgoing
+ * events.
+ *
+ * @return void
+ */
+ public function handleEvents()
+ {
+ foreach ($this->connections as $connection) {
+ $this->driver->setConnection($connection);
+ $this->plugins->setConnection($connection);
+ $this->plugins->onTick();
+
+ if ($event = $this->driver->getEvent()) {
+ $this->ui->onEvent($event, $connection);
+ $this->plugins->setEvent($event);
+
+ if (!$this->plugins->preEvent()) {
+ continue;
+ }
+
+ $this->plugins->{'on' . ucfirst($event->getType())}();
+ }
+
+ $this->processEvents($connection);
+ }
+ }
+}
--- /dev/null
+-----------------------------------------------------------------------------
+-- About LogViewer
+-----------------------------------------------------------------------------
+
+The purpose of this tool is to make an HTML display of the Logs made by the
+Logging plugin for Phergie stored in SQLite database.
+
+-----------------------------------------------------------------------------
+-- Installation
+-----------------------------------------------------------------------------
+
+To install this, simply copy the contents of this directory into whatever
+directory you want this to run under.
+
+-----------------------------------------------------------------------------
+-- Configuration
+-----------------------------------------------------------------------------
+
+The only configuration needed at this point in time is to edit the $database
+variable in the top of the index.php to point to the appropriate Phergie
+log file.
+
+Stuff may get more complicated in the future ;) But this is meant to be
+simple for now.
\ No newline at end of file
--- /dev/null
+<?php
+// Phergie Log Viewer ... Currently designed as a single PHP file in order to make it easy to
+// 'install' this. Just drop the index.php (or whatever name you wish to rename it to) wherever
+// you wish, and it will simply work. Sure, it would be nice to structure some of this stuff into
+// various include files/etc. But right now this is simple enough of a quick log viewer, that it's
+// just one file.
+
+
+/********** SETUP **********/
+
+// (Change any of these if/as needed for your setup)
+ini_set('default_charset', 'UTF-8');
+date_default_timezone_set('UTC');
+$log = "/PATH/AND/FILENAME/TO/YOUR/LOGFILE/PLEASE.db";
+
+
+/********** PREPARATION **********/
+
+$db = new PDO('sqlite:' . $log);
+if (!is_object($db)) {
+ // Failure, can't access Phergie Log. Bail with an error message, not pretty, but works:
+ echo "ERROR: Cannot access Phergie Log File, please check the configuration & access privileges";
+ exit();
+}
+
+
+/********** DETECTION **********/
+
+// Determine the mode of the application and call the appropriate handler function
+$mode = empty($_GET['m']) ? '' : $_GET['m'];
+switch ($mode) {
+ case 'channel':
+ show_days($db);
+ break;
+ case 'day':
+ show_log($db);
+ break;
+ default:
+ show_channels($db);
+}
+
+// Exit not really needed here, but reminds us that everything below is support functions:
+exit();
+
+
+/********** MODES **********/
+
+/**
+ * show_channels
+ *
+ * Provide a list of all channel's that we are logging information for:
+ *
+ * @param PDO A PDO object referring to the database
+ * @return void
+ * @author Eli White <eli@eliw.com>
+ **/
+function show_channels(PDO $db) {
+ // Begin the HTML page:
+ template_header('Channels');
+ echo "\nChannels:\n<ul>\n";
+
+ // Loop through the database reading in each channel, and echoing out a <li> for it.
+ // only grab actual channels that start with # ... also pre-lowercase everything.
+ // this allows us to 'deal' with variable caps in how the channels were logged.
+ $channels = $db->query("
+ select distinct lower(chan) as c
+ from logs
+ where chan like '#%'
+ ");
+ foreach ($channels as $row) {
+ $html = utf8specialchars($row['c']);
+ $url = urlencode($row['c']);
+ echo "<li><a href=\"?m=channel&w={$url}\">{$html}</a></li>\n";
+ }
+
+ // Finish off the page:
+ echo "\n</ul>\n";
+ template_footer();
+}
+
+/**
+ * show_days
+ *
+ * Create a calendar view of all days available for this particular channel
+ *
+ * NOTE: May get unwieldy if large log files. Perhaps consider in the future
+ * making a paginated version of this? by year? Or a separate 'which year' page
+ * before this? Not to worry about now.
+ *
+ * @param PDO A PDO object referring to the database
+ * @return void
+ * @author Eli White <eli@eliw.com>
+ **/
+function show_days(PDO $db) {
+ $channel = $_GET['w'];
+ $url = urlencode($channel);
+
+ // Begin the HTML page:
+ template_header('Daily Logs for Channel: ' . utf8specialchars($channel));
+ echo "\n<ul>\n";
+
+ // Query the database to discover all days that are available for this channel:
+ $data = array();
+ $prepared = $db->prepare("
+ select distinct date(tstamp) as day
+ from logs
+ where lower(chan) = :chan
+ ");
+ $prepared->execute(array(':chan' => $channel));
+ foreach ($prepared as $row) {
+ list($y, $m, $d) = explode('-', $row['day']);
+ $data[$y][$m][$d] = "{$y}-{$m}-{$d}";
+ }
+
+ // For now, just loop over them all and provide a list:
+ ksort($data);
+ foreach ($data as $year => $months) {
+ ksort($months);
+ foreach ($months as $month => $days) {
+ // Figure out a few facts about this month:
+ $stamp = mktime(0, 0, 0, $month, 1, $year);
+ $first_weekday = idate('w', $stamp);
+ $days_in_month = idate('t', $stamp);
+ $name = date('F', $stamp);
+
+ // We have a month ... start a new table:
+ echo <<<EOTABLE
+<div class="month">
+ <table>
+ <caption>{$name} {$year}</caption>
+ <tr><th>Sun</th><th>Mon</th><th>Tue</th><th>Wed</th><th>Thu</th><th>Fri</th><th>Sat</th></tr>
+EOTABLE;
+ // Now we need to start looping through the days in this month:
+ echo '<tr>';
+ $rowmod = 0;
+ // Loop through all day entries, no matter how many blanks we need:
+ for ($d = (-$first_weekday + 1); $d < $days_in_month + 1; $d++) {
+ if (!($rowmod++ % 7)) {
+ // Stop/start a new row:
+ echo '</tr><tr>';
+ }
+ echo '<td>';
+ // If this day is pre or post actual month days, make it blank:
+ if (($d < 1) || ($d > $days_in_month)) {
+ echo ' ';
+ } elseif (isset($days[$d])) {
+ // Make a link to the day's log:
+ echo "<a href=\"?m=day&w={$url}&d={$days[$d]}\">{$d}</a>";
+ } else {
+ // Just a dead number:
+ echo $d;
+ }
+ echo '</td>';
+ }
+ // Finish off any blanks needed for a complete table row:
+ while ($rowmod++ % 7) {
+ echo '<td> </td>';
+ }
+ echo "</tr></table></div>\n";
+ }
+ }
+
+ // Finish off the page:
+ echo "\n</ul>\n";
+ template_footer();
+}
+
+/**
+ * show_log
+ *
+ * Actually show the log for this specific day
+ *
+ * @param PDO A PDO object referring to the database
+ * @return void
+ * @author Eli White <eli@eliw.com>
+ **/
+function show_log(PDO $db) {
+ $channel = $_GET['w'];
+ $day = $_GET['d'];
+ $parts = explode('-', $day);
+ $formatted_date = "{$parts[0]}-{$parts[1]}-{$parts[2]}";
+
+ // Begin the HTML page:
+ template_header('Date: ' . utf8specialchars($formatted_date) .
+ ' - Channel: ' . utf8specialchars($channel));
+
+ // Query the database to get all log lines for this date:
+ $prepared = $db->prepare("
+ select time(tstamp) as t, type, nick, message
+ from logs
+ where lower(chan) = :chan and date(tstamp) = :day
+ order by tstamp asc
+ ");
+ $prepared->execute(array(
+ ':chan' => $channel,
+ ':day' => $day,
+ ));
+
+ // Loop through each line,
+ foreach ($prepared as $row) {
+ // Prepare some basic details for output:
+ $color = nick_color($row['nick']);
+ $time = utf8specialchars($row['t']);
+ $msg = utf8specialchars($row['message']);
+ $nick = utf8specialchars($row['nick']);
+ $type = false;
+
+ // Now change the format of the line based upon the type:
+ switch ($row['type']) {
+ case 4: // PRIVMSG (A Regular Message)
+ echo "[$time] <span style=\"color:#{$color};\"><{$nick}></span> {$msg}<br />\n";
+ break;
+ case 5: // ACTION (emote)
+ echo "[$time] <span style=\"color:#{$color};\">*{$nick} {$msg}</span><br />\n";
+ break;
+ case 1: // JOIN
+ echo "[$time] -> {$nick} joined the room.<br />\n";
+ break;
+ case 2: // PART (leaves channel)
+ echo "[$time] -> {$nick} left the room: {$msg}<br />\n";
+ break;
+ case 3: // QUIT (quits the server)
+ echo "[$time] -> {$nick} left the server: {$msg}<br />\n";
+ break;
+ case 6: // NICK (changes their nickname)
+ echo "[$time] -> {$nick} is now known as: {$msg}<br />\n";
+ break;
+ case 7: // KICK (booted)
+ echo "[$time] -> {$nick} boots {$msg} from the room.<br />\n";
+ break;
+ case 8: // MODE (changed their mode)
+ $type = 'MODE';
+ case 9: // TOPIC (changed the topic)
+ $type = $type ? $type : 'TOPIC';
+ echo "[$time] -> {$nick}: :{$type}: {$msg}<br />\n";
+ }
+ }
+
+ // Finish up the page:
+ template_footer();
+}
+
+/**
+ * nick_color
+ *
+ * Uses a silly little algorithm to pick a consistent but unique(ish) color for
+ * any given username. NOTE: Augment this in the future to make it not generate
+ * 'close to white' ones, also maybe to ensure uniqueness? (Not allow two to have
+ * colors that are close to each other?)
+ *
+ * @return string A CSS valid hex color string
+ * @author Eli White <eli@eliw.com>
+ **/
+function nick_color($user) {
+ static $colors = array();
+
+ if (!isset($colors[$user])) {
+ $colors[$user] = substr(md5($user), 0, 6);
+ }
+
+ return $colors[$user];
+}
+
+/**
+ * utf8specialchars
+ *
+ * Just a quick wrapper around htmlspecialchars
+ *
+ * @param string The UTF8 string to escape
+ * @return string An escaped and ready for HTML use string
+ * @author Eli White <eli@eliw.com>
+ **/
+function utf8specialchars($string) {
+ return htmlspecialchars($string, ENT_COMPAT, 'UTF-8');
+}
+
+
+/********** TEMPLATES **********/
+
+/**
+ * template_header
+ *
+ * Echo out the header for each HTML page
+ *
+ * @param $title string The title to be used for this page.
+ * @return void
+ * @author Eli White <eli@eliw.com>
+ **/
+function template_header($title) {
+ $css = template_css();
+ echo <<<EOHTML
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <title>Phergie LogViewer - {$title}</title>
+ <style type="text/css" media="all">{$css}</style>
+ </head>
+ <body>
+ <h2>Phergie LogViewer - {$title}</h2>
+EOHTML;
+}
+
+/**
+ * template_footer
+ *
+ * Echo out the bottom of each HTML page
+ *
+ * @return void
+ * @author Eli White <eli@eliw.com>
+ **/
+function template_footer() {
+ echo <<<EOHTML
+ </body>
+</html>
+EOHTML;
+}
+
+/**
+ * template_css
+ *
+ * Generate the CSS used by these HTML pages & return it.
+ *
+ * @return string The CSS in question:
+ * @author Eli White <eli@eliw.com>
+ **/
+function template_css() {
+ return <<<EOCSS
+ div.month {
+ float: left;
+ height: 15em;
+ }
+
+ div.month table {
+ border-collapse: collapse;
+ border: 2px solid black;
+ margin-right: 2em;
+ }
+
+ div.month td, div.month th {
+ text-align: center;
+ vertical-align: bottom;
+ border: 1px solid gray;
+ width: 2em;
+ height: 1.7em;
+ padding: 1px;
+ margin: 0px;
+ }
+
+ div.month th {
+ text-decoration: bold;
+ border: 2px solid black;
+ }
+
+ div.month a {
+ text-decoration: none;
+ }
+
+ a:visited, a:link {
+ color: blue;
+ }
+
+ a:active, a:hover {
+ color: red;
+ }
+EOCSS;
+}
--- /dev/null
+This directory holds any assorted tools that work with Phergie but that are
+not part of Phergie itself. Such as tools to examine data that Phergie is
+capturing/logging.
+
+Each should live inside of it's own directory, and be completely self-contained
+with instructions inside on how to use it.
--- /dev/null
+<?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 end-user interfaces.
+ *
+ * @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_Ui_Abstract
+{
+ /**
+ * Handler for when a server connection is attempted.
+ *
+ * @param string $host Server hostname
+ *
+ * @return void
+ */
+ public function onConnect($host)
+ {
+ }
+
+ /**
+ * Handler for when an attempt is made to load a plugin.
+ *
+ * @param string $plugin Short name of the plugin
+ *
+ * @return void
+ */
+ public function onPluginLoad($plugin)
+ {
+ }
+
+ /**
+ * Handler for when a plugin fails to load.
+ *
+ * @param string $plugin Short name of the plugin
+ * @param string $message Message describing the reason for the failure
+ *
+ * @return void
+ */
+ public function onPluginFailure($plugin, $message)
+ {
+ }
+
+ /**
+ * Handler for when the bot receives an IRC event.
+ *
+ * @param Phergie_Event_Abstract $event Received event
+ * @param Phergie_Connection $connection Connection on which the event
+ * was received
+ *
+ * @return void
+ */
+ public function onEvent(Phergie_Event_Abstract $event,
+ Phergie_Connection $connection
+ ) {
+ }
+
+ /**
+ * Handler for when the bot sends a command to a server.
+ *
+ * @param Phergie_Event_Command $event Event representing the command
+ * being sent
+ * @param Phergie_Connection $connection Connection on which the command
+ * is being sent
+ *
+ * @return void
+ */
+ public function onCommand(Phergie_Event_Command $event,
+ Phergie_Connection $connection
+ ) {
+ }
+
+ /**
+ * Handler for when the bot terminates a connection to a server.
+ *
+ * @param Phergie_Connection $connection Terminated connection
+ *
+ * @return void
+ */
+ public function onQuit(Phergie_Connection $connection)
+ {
+ }
+
+ /**
+ * Handler for when the bot shuts down after terminating all server
+ * connections.
+ *
+ * @return void
+ */
+ public function onShutdown()
+ {
+ }
+}
--- /dev/null
+<?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
+ */
+
+/**
+ * End-user interface that produces console output when running the bot from
+ * a shell.
+ *
+ * @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_Ui_Console extends Phergie_Ui_Abstract
+{
+ /**
+ * Flag that toggles all console output
+ *
+ * @var bool
+ */
+ protected $enabled;
+
+ /**
+ * Format for timestamps included in console output
+ *
+ * @var string
+ * @link http://php.net/date
+ */
+ protected $format;
+
+ /**
+ * Constructor to initialize object properties.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ $this->enabled = true;
+ $this->format = 'H:i:s';
+ }
+
+ /**
+ * Outputs a timestamped line to the console if console output is enabled.
+ *
+ * @param string $line Line to output
+ *
+ * @return void
+ */
+ protected function console($line)
+ {
+ if ($this->enabled) {
+ echo date($this->format), ' ', $line, PHP_EOL;
+ }
+ }
+
+ /**
+ * Returns whether console output is enabled.
+ *
+ * @return bool TRUE if console output is enabled, FALSE otherwise
+ */
+ public function isEnabled()
+ {
+ return $this->enabled;
+ }
+
+ /**
+ * Sets whether console output is enabled.
+ *
+ * @param bool $enabled TRUE to enable console output, FALSE otherwise,
+ * defaults to TRUE
+ *
+ * @return Phergie_Ui_Console Provides a fluent interface
+ */
+ public function setEnabled($enabled = true)
+ {
+ $this->enabled = (bool) $enabled;
+ return $this;
+ }
+
+ /**
+ * Returns the format used for timestamps in console output.
+ *
+ * @return string
+ * @link http://php.net/date
+ */
+ public function getFormat()
+ {
+ return $this->format;
+ }
+
+ /**
+ * Sets the format used for timestamps in console output, overwriting
+ * any previous format used.
+ *
+ * @param string $format Timestamp format
+ *
+ * @return Phergie_Ui_Console Provides a fluent interface
+ * @link http://php.net/date
+ */
+ public function setFormat($format)
+ {
+ $this->format = (string) $format;
+ return $this;
+ }
+
+ /**
+ * Outputs a prompt when a server connection is attempted.
+ *
+ * @param string $host Server hostname
+ *
+ * @return void
+ */
+ public function onConnect($host)
+ {
+ $this->console('Connecting to ' . $host);
+ }
+
+ /**
+ * Outputs a prompt when a plugin is loaded successfully.
+ *
+ * @param string $plugin Short name of the plugin
+ *
+ * @return void
+ */
+ public function onPluginLoad($plugin)
+ {
+ $this->console('Loaded plugin ' . $plugin);
+ }
+
+ /**
+ * Outputs a prompt when a plugin fails to load.
+ *
+ * @param string $plugin Short name of the plugin
+ * @param string $message Message describing the reason for the failure
+ *
+ * @return void
+ */
+ public function onPluginFailure($plugin, $message)
+ {
+ $this->console('Unable to load plugin ' . $plugin . ' - ' . $message);
+ }
+
+ /**
+ * Outputs a prompt when the bot receives an IRC event.
+ *
+ * @param Phergie_Event_Abstract $event Received event
+ * @param Phergie_Connection $connection Connection on which the
+ * event was received
+ *
+ * @return void
+ */
+ public function onEvent(Phergie_Event_Abstract $event,
+ Phergie_Connection $connection
+ ) {
+ $host = $connection->getHostmask()->getHost();
+ $this->console($host . ' <- ' . $event->getRawData());
+ }
+
+ /**
+ * Outputs a prompt when the bot sends a command to a server.
+ *
+ * @param Phergie_Event_Command $event Event representing the
+ * command being sent
+ * @param Phergie_Connection $connection Connection on which the
+ * command is being sent
+ *
+ * @return void
+ */
+ public function onCommand(Phergie_Event_Command $event,
+ Phergie_Connection $connection
+ ) {
+ $plugin = $event->getPlugin()->getName();
+ $host = $connection->getHostmask()->getHost();
+ $type = strtoupper($event->getType());
+ $args = implode(' ', $event->getArguments());
+ $this->console(
+ $plugin . ' plugin: ' .
+ $host . ' -> ' . $type . ' ' . $args
+ );
+ }
+
+ /**
+ * Outputs a prompt when the bot terminates a connection to a server.
+ *
+ * @param Phergie_Connection $connection Terminated connection
+ *
+ * @return void
+ */
+ public function onQuit(Phergie_Connection $connection)
+ {
+ $host = $connection->getHostmask()->getHost();
+ $this->console('Disconnecting from ' . $host);
+ }
+
+ /**
+ * Outputs a prompt when the bot shuts down after terminating all server
+ * connections.
+ *
+ * @return void
+ */
+ public function onShutdown()
+ {
+ $this->console('Shutting down');
+ }
+}
--- /dev/null
+<?php
+
+require_once 'phing/tasks/ext/PearPackage2Task.php';
+
+class PhergiePackageTask extends PearPackage2Task
+{
+ protected function setOptions()
+ {
+ $this->pkg->addMaintainer('lead', 'team', 'Phergie Development Team', 'team@phergie.org');
+
+ $path = str_replace('_', '/', $this->package) . '.php';
+ if (file_exists($path)) {
+ $contents = file_get_contents($path);
+ preg_match_all('#/\*\*(.*)\*/#Ums', $contents, $matches, PREG_SET_ORDER);
+ $doc = $matches[1][1];
+
+ $have_summary = false;
+ $have_description = false;
+ foreach ($this->options as $option) {
+ switch ($option->getName()) {
+ case 'summary':
+ $have_summary = true;
+ break;
+ case 'description':
+ $have_descripion = true;
+ break;
+ }
+ }
+
+ if (!$have_summary || !$have_description) {
+ $description = substr($doc, 0, strpos($doc, '@'));
+ $description = trim(preg_replace(array('#^[\h*]*|[\h*]*$#m', '#[\h]+#m'), array('', ' '), $description));
+ $split = preg_split('/\v\v+/', $description);
+ $summary = trim(array_shift($split));
+ if (!$have_summary) {
+ $this->pkg->setSummary(htmlentities($summary, ENT_QUOTES));
+ }
+ if (!$have_description) {
+ $this->pkg->setDescription(htmlentities($description, ENT_QUOTES));
+ }
+ }
+
+ $doc = preg_split('/\v+/', $doc);
+ $doc = preg_grep('/@uses/', $doc);
+ $doc = preg_replace('/\s*\* @uses\s+|\s+$/', '', $doc);
+ foreach ($doc as $line) {
+ if (strpos($line, 'extension') === 0) {
+ $line = explode(' ', $line);
+ $name = $line[1];
+ $optional = 'required';
+ if (isset($line[2])) {
+ $optional = $line[2];
+ }
+ $this->pkg->addExtensionDep(
+ $optional,
+ $name
+ );
+ } else {
+ $line = explode(' ', $line);
+ $name = $line[0];
+ $channel = $line[1];
+ $optional = 'required';
+ if (isset($line[2])) {
+ $optional = $line[2];
+ }
+ $this->pkg->addPackageDepWithChannel(
+ $optional,
+ $name,
+ $channel
+ );
+ }
+ }
+ }
+
+ $newmap = array();
+ foreach ($this->mappings as $key => $map) {
+ switch ($map->getName()) {
+ case 'releases':
+ $releases = $map->getValue();
+ foreach ($releases as $release) {
+ $this->pkg->addRelease();
+ if (isset($release['installconditions'])) {
+ if (isset($release['installconditions']['os'])) {
+ $this->pkg->setOsInstallCondition($release['installconditions']['os']);
+ }
+ }
+ if (isset($release['filelist'])) {
+ if (isset($release['filelist']['install'])) {
+ foreach ($release['filelist']['install'] as $file => $as) {
+ $this->pkg->addInstallAs($file, $as);
+ }
+ }
+ if (isset($release['filelist']['ignore'])) {
+ foreach ($release['filelist']['ignore'] as $file) {
+ $this->pkg->addIgnoreToRelease($file);
+ }
+ }
+ }
+ }
+ break;
+
+ default:
+ $newmap[] = $map;
+ }
+ }
+ $this->mappings = $newmap;
+
+ parent::setOptions();
+ }
+}
--- /dev/null
+Phergie is an IRC bot written for PHP 5.2.
+
+Main project web site: http://phergie.org
+
+Instructions for running your own instance of Phergie: http://phergie.org/users/
+
+Architectural overview for plugin developers: http://phergie.org/developers/
+
+Support: http://phergie.org/support/
+
+Bug reports/feature requests: http://github.com/elazar/phergie/issues
--- /dev/null
+<?php
+
+return array(
+
+ // One array per connection, pretty self-explanatory
+ 'connections' => array(
+ // Ex: All connection info for the Freenode network
+ array(
+ 'host' => 'irc.freenode.net',
+ 'port' => 6667,
+ 'username' => 'Elazar',
+ 'realname' => 'Matthew Turland',
+ 'nick' => 'Phergie2',
+ // 'password' => 'password goes here if needed',
+ // 'transport' => 'ssl' // uncomment to connect using SSL
+ )
+ ),
+
+ 'processor' => 'async',
+ 'processor.options' => array('usec' => 200000),
+ // Time zone. See: http://www.php.net/manual/en/timezones.php
+ 'timezone' => 'UTC',
+
+ // Whitelist of plugins to load
+ 'plugins' => array(
+ // To enable a plugin, simply add a string to this array containing
+ // the short name of the plugin as shown below.
+
+ // 'ShortPluginName',
+
+ // Below is an example of enabling the AutoJoin plugin, for which
+ // the corresponding PEAR package is Phergie_Plugin_AutoJoin. This
+ // plugin allows you to set a list of channels in this configuration
+ // file that the bot will automatically join when it connects to a
+ // server. If you'd like to enable this plugin, simply install it,
+ // uncomment the line below, and set a value for the setting
+ // autojoin.channels (examples for which are located further down in
+ // this file).
+
+ // 'AutoJoin',
+
+ // A few other recommended plugins:
+
+ // Servers randomly send PING events to clients to ensure that
+ // they're still connected and will eventually terminate the
+
+ // connection if a PONG response is not received. The Pong plugin
+ // handles sending these responses.
+
+ // 'Pong',
+
+ // It's sometimes difficult to distinguish between a lack of
+ // activity on a server and the client not receiving data even
+ // though a connection remains open. The Ping plugin performs a self
+ // CTCP PING sporadically to ensure that its connection is still
+ // functioning and, if not, terminates the bot.
+
+ // 'Ping',
+
+ // Sometimes it's desirable to have the bot disconnect gracefully
+ // when issued a command to do so via a PRIVMSG event. The Quit
+ // plugin implements this using the Command plugin to intercept the
+ // command.
+
+ // 'Quit',
+ ),
+
+ // If set to true, this allows any plugin dependencies for plugins
+ // listed in the 'plugins' option to be loaded even if they are not
+ // explicitly included in that list
+ 'plugins.autoload' => true,
+
+ // Enables shell output describing bot events via Phergie_Ui_Console
+ 'ui.enabled' => true,
+
+ // Examples of supported values for autojoins.channel:
+ // 'autojoin.channels' => '#channel1,#channel2',
+ // 'autojoin.channels' => array('#channel1', '#channel2'),
+ // 'autojoin.channels' => array(
+ // 'host1' => '#channel1,#channel2',
+ // 'host2' => array('#channel3', '#channel4')
+ // ),
+
+ // Examples of setting values for Ping plugin settings
+
+ // This is the amount of time in seconds that the Ping plugin will wait
+ // to receive an event from the server before it initiates a self-ping
+
+ // 'ping.event' => 300, // 5 minutes
+
+ // This is the amount of time in seconds that the Ping plugin will wait
+ // following a self-ping attempt before it assumes that a response will
+ // never be received and terminates the connection
+
+ // 'ping.ping' => 10, // 10 seconds
+
+);
--- /dev/null
+<?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_Tests
+ * @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_Tests
+ */
+
+/**
+ * Unit test suite for Pherge_Plugin_Handler.
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+class Phergie_Plugin_HandlerTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * Plugin handler instance being tested
+ *
+ * @var Phergie_Plugin_Handler
+ */
+ protected $handler;
+
+ /**
+ * Sets up a new handler instance before each test.
+ *
+ * @return void
+ */
+ public function setUp()
+ {
+ $this->handler = new Phergie_Plugin_Handler(
+ new Phergie_Config(),
+ new Phergie_Event_Handler()
+ );
+ }
+
+ /**
+ * Destroys the handler instance after each test
+ *
+ * @return void
+ */
+ public function tearDown()
+ {
+ unset($this->handler);
+ }
+
+ /**
+ * Ensures that we can iterate over the handler
+ *
+ * @return void
+ */
+ public function testImplementsIterator()
+ {
+ $reflection = new ReflectionObject($this->handler);
+ $this->assertTrue(
+ $reflection->implementsInterface('IteratorAggregate')
+ );
+
+ $this->assertType(
+ 'Iterator', $this->handler->getIterator(),
+ 'getIterator() must actually return an Iterator'
+ );
+ }
+
+ /**
+ * Ensures a newly instantiated handler does not have plugins associated
+ * with it
+ *
+ * @depends testImplementsIterator
+ * @return void
+ */
+ public function testEmptyHandlerHasNoPlugins()
+ {
+ $count = 0;
+ foreach ($this->handler as $plugin) {
+ $count++;
+ }
+
+ $this->assertEquals(0, $count);
+ }
+
+ /**
+ * Ensures a newly instantiated handler does not default to autoload
+ *
+ * @return void
+ */
+ public function testDefaultsToNotAutoload()
+ {
+ $this->assertFalse($this->handler->getAutoload());
+ }
+
+ /**
+ * addPath provides a fluent interface
+ *
+ * @return void
+ */
+ public function testAddPathProvidesFluentInterface()
+ {
+ $handler = $this->handler->addPath(dirname(__FILE__));
+ $this->assertSame($this->handler, $handler);
+ }
+
+ /**
+ * addPath throws an exception when it cannot read the directory
+ *
+ * @return void
+ */
+ public function testAddPathThrowsExceptionOnUnreadableDirectory()
+ {
+ try {
+ $this->handler->addPath('/an/unreadable/directory/path');
+ } catch(Phergie_Plugin_Exception $e) {
+ $this->assertEquals(
+ Phergie_Plugin_Exception::ERR_DIRECTORY_NOT_READABLE,
+ $e->getCode()
+ );
+ return;
+ }
+
+ $this->fail('An expected exception has not been raised.');
+ }
+
+ /**
+ * adds a path into the plugin handler and then ensures that files
+ * in that location can be found
+ *
+ * @return void
+ */
+ public function testAddPath()
+ {
+ $plugin_name = 'Mock';
+ try {
+ $this->handler->addPlugin($plugin_name);
+ } catch(Phergie_Plugin_Exception $e) {
+ $this->assertEquals(
+ Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND,
+ $e->getCode()
+ );
+
+ $this->handler->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+
+ try {
+ $this->handler->addPlugin($plugin_name);
+ } catch(Phergie_Plugin_Exception $e) {
+ $this->fail(
+ 'After adding the directory, the plugin was still '
+ . 'not found.'
+ );
+ }
+
+ return;
+ }
+
+ $this->fail(
+ 'Before adding the directory, an expected exception '
+ . 'was not raised'
+ );
+ }
+
+ /**
+ * addPlugin returns the plugin instance that was added
+ *
+ * @return void
+ */
+ public function testAddPluginByInstanceReturnsPluginInstance() {
+ $plugin = $this->getMock('Phergie_Plugin_Abstract');
+ $plugin
+ ->expects($this->any())
+ ->method('getName')
+ ->will($this->returnValue('TestPlugin'));
+
+ $returned_plugin = $this->handler->addPlugin($plugin);
+ $this->assertSame(
+ $returned_plugin,
+ $plugin,
+ 'addPlugin returns the same instance that is passed to it'
+ );
+ }
+
+ /**
+ * Can add a plugin to the handler by shortname
+ *
+ * @return void
+ */
+ public function testAddPluginToHandlerByShortname()
+ {
+ $plugin_name = 'Mock';
+ $this->handler->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+
+ $returned_plugin = $this->handler->addPlugin($plugin_name);
+ $this->assertTrue($this->handler->hasPlugin($plugin_name));
+ $this->assertType(
+ 'Phergie_Plugin_Mock',
+ $this->handler->getPlugin($plugin_name)
+ );
+ $this->assertSame(
+ $this->handler->getPlugin($plugin_name),
+ $returned_plugin,
+ 'Handler contains plugin when added by shortname.'
+ );
+ }
+
+
+ /**
+ * Can add a plugin to the handler by instance
+ *
+ * @return void
+ */
+ public function testAddPluginToHandlerByInstance()
+ {
+ $plugin = $this->getMock('Phergie_Plugin_Abstract');
+ $plugin
+ ->expects($this->any())
+ ->method('getName')
+ ->will($this->returnValue('TestPlugin'));
+
+ $returned_plugin = $this->handler->addPlugin($plugin);
+
+ $this->assertTrue($this->handler->hasPlugin('TestPlugin'));
+ $this->assertSame(
+ $plugin, $returned_plugin,
+ 'addPlugin returns the same plugin'
+ );
+ $this->assertSame(
+ $plugin, $this->handler->getPlugin('TestPlugin'),
+ 'getPlugin returns the same plugin'
+ );
+ }
+
+ /**
+ * addPlugin throws an exception when it can't find the plugin
+ *
+ * @return void
+ */
+ public function testAddPluginThrowsExceptionIfCannotFindPlugin()
+ {
+ try {
+ $this->handler->addPlugin('TestPlugin');
+ } catch(Phergie_Plugin_Exception $e) {
+ $this->assertEquals(
+ Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND,
+ $e->getCode()
+ );
+ return;
+ }
+
+ $this->fail('An expected exception has not been raised.');
+ }
+
+ /**
+ * addPlugin throws an exception when trying to instantiate a
+ * class that doesn't extend from Phergie_Plugin_Abstract
+ *
+ * @return void
+ */
+ public function testAddPluginThrowsExceptionIfRequestingNonPlugin()
+ {
+ try {
+ $this->handler->addPlugin('Handler');
+ } catch(Phergie_Plugin_Exception $e) {
+ $this->assertEquals(
+ Phergie_Plugin_Exception::ERR_INCORRECT_BASE_CLASS,
+ $e->getCode()
+ );
+ return;
+ }
+
+ $this->fail('An expected exception has not been raised.');
+ }
+
+ /**
+ * addPlugin throws an exception when trying to instantiate a
+ * class that can't be instantiated.
+ *
+ * @return void
+ */
+ public function testAddPluginThrowsExceptionIfPluginNotInstantiable()
+ {
+ $this->handler->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+ try {
+ $this->handler->addPlugin('TestNonInstantiablePluginFromFile');
+ } catch(Phergie_Plugin_Exception $e) {
+ $this->assertEquals(
+ Phergie_Plugin_Exception::ERR_CLASS_NOT_INSTANTIABLE,
+ $e->getCode()
+ );
+ return;
+ }
+
+ $this->fail('An expected exception has not been raised.');
+ }
+
+ /**
+ * addPlugin with shortname and arguments passes args to constructor
+ *
+ * @return null
+ */
+ public function testAddPluginShortnamePassesArgsToConstructor()
+ {
+ $plugin_name = 'Mock';
+ $this->handler->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+
+ $arguments = array('a', 'b', 'c');
+
+ $plugin = $this->handler->addPlugin($plugin_name, $arguments);
+ $this->assertAttributeSame(
+ $arguments,
+ 'args',
+ $plugin,
+ 'Arguments passed in to addPlugin match the arguments '
+ . 'the Mock plugin constructor received'
+ );
+ }
+
+ /**
+ * addPlugin passes Phergie_Config to instantiated plugin
+ *
+ * @return null
+ */
+ public function testAddPluginPassesPhergieConfigToInstantiatedPlugin()
+ {
+ $my_config = new Phergie_Config();
+ $my_config['my_option'] = 'my_value';
+
+ // create a new handler with this config
+ unset($this->handler);
+ $this->handler = new Phergie_Plugin_Handler(
+ $my_config,
+ new Phergie_Event_Handler()
+ );
+
+ $plugin_name = 'Mock';
+ $this->handler->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+
+ $plugin = $this->handler->addPlugin($plugin_name);
+
+ $this->assertSame(
+ $my_config,
+ $plugin->getConfig(),
+ 'addPlugin passes Phergie_Config to instantiated plugin'
+ );
+ }
+
+ /**
+ * addPlugin passes Phergie_Event_Handler to instantiated plugin
+ *
+ * @return null
+ */
+ public function testAddPluginPassesPhergieEventHandlerToInstantiatedPlugin()
+ {
+ $plugin = $this->getMock('Phergie_Plugin_Abstract');
+ $plugin
+ ->expects($this->any())
+ ->method('getName')
+ ->will($this->returnValue('TestPlugin'));
+
+ $my_event_handler = new Phergie_Event_Handler();
+ $my_event_handler->addEvent($plugin, 'ping');
+
+ // create a new plugin handler with this event handler
+ unset($this->handler);
+ $this->handler = new Phergie_Plugin_Handler(
+ new Phergie_Config(),
+ $my_event_handler
+ );
+
+ $plugin_name = 'Mock';
+ $this->handler->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+
+ $plugin = $this->handler->addPlugin($plugin_name);
+
+ $this->assertSame(
+ $my_event_handler,
+ $plugin->getEventHandler(),
+ 'addPlugin passes Phergie_Event_Handler to instantiated plugin'
+ );
+ }
+
+ /**
+ * @todo addPlugin calls onLoad() to instantiated plugin
+ */
+
+ /**
+ * implements __isset
+ *
+ * @return void
+ */
+ public function testPluginHandlerImplementsIsset()
+ {
+ $plugin_name = 'TestPlugin';
+
+ $this->assertFalse(isset($this->handler->{$plugin_name}));
+
+ $plugin = $this->getMock('Phergie_Plugin_Abstract');
+ $plugin
+ ->expects($this->any())
+ ->method('getName')
+ ->will($this->returnValue($plugin_name));
+
+ $this->handler->addPlugin($plugin);
+
+ $this->assertTrue(isset($this->handler->{$plugin_name}));
+
+ }
+
+ /**
+ * addPlugin() returns the same plugin when requested twice
+ *
+ * @return void
+ */
+ public function testAddPluginReturnsSamePluginWhenAskedTwice()
+ {
+ $plugin_name = 'Mock';
+ $this->handler->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+
+ $plugin1 = $this->handler->addPlugin($plugin_name);
+ $plugin2 = $this->handler->addPlugin($plugin_name);
+ $this->assertSame($plugin1, $plugin2);
+ }
+
+
+ /**
+ * Tests an exception is thrown when trying to get a plugin
+ * that is not already loaded and autoload is off
+ *
+ * @depends testDefaultsToNotAutoload
+ * @return void
+ */
+ public function testExceptionThrownWhenLoadingPluginWithoutAutoload()
+ {
+ $this->handler->addPath(dirname(__FILE__), 'Phergie_Plugin_');
+
+ try {
+ $this->handler->getPlugin('Mock');
+ } catch (Phergie_Plugin_Exception $expected) {
+ $this->assertEquals(
+ Phergie_Plugin_Exception::ERR_PLUGIN_NOT_LOADED,
+ $expected->getCode()
+ );
+ return;
+ }
+
+ $this->fail('An expected exception has not been raised.');
+ }
+}
--- /dev/null
+<?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_Tests
+ * @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_Tests
+ */
+
+/**
+ * Creates a plugin on the filesystem that can be used by
+ * Phergie_Plugin_Handler's addPath utility to be located and loaded.
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+class Phergie_Plugin_Mock extends Phergie_Plugin_Abstract
+{
+ /**
+ * holds the arguments that were passed in to the constructor
+ * @var array
+ */
+ protected $args;
+
+ /**
+ * processes a variable number of arguments into the args property
+ *
+ * @return null
+ */
+ public function __construct()
+ {
+ $this->args = func_get_args();
+ }
+}
--- /dev/null
+<?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
+ */
+
+require_once(dirname(__FILE__) . '/TestCase.php');
+
+/**
+ * Unit test suite for Pherge_Plugin_Ping.
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @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_PingTest extends Phergie_Plugin_TestCase
+{
+ protected $config = array('ping.ping' => 10,
+ 'ping.event' => 300);
+
+ /**
+ * Sets up the fixture, for example, opens a network connection.
+ * This method is called before a test is executed.
+ */
+ protected function setUp()
+ {
+ $this->setPlugin(new Phergie_Plugin_Ping);
+ }
+
+ /**
+ * Test the lastEvent setter and getter
+ */
+ public function testSetGetLastEvent()
+ {
+ $expected = rand(100000,200000);
+ $this->plugin->setLastEvent($expected);
+ $this->assertEquals($expected,
+ $this->plugin->getLastEvent(),
+ 'Assert that the last event was set and gotten ' .
+ 'correctly');
+ }
+
+ /**
+ * Test the lastPing setter and getter
+ */
+ public function testSetGetLastPing()
+ {
+
+ $expected = rand(100000,200000);
+ $this->plugin->setLastPing($expected);
+ $this->assertEquals($expected,
+ $this->plugin->getLastPing(),
+ 'Assert that the last ping was set and gotten ' .
+ 'correctly');
+ }
+
+ /**
+ * Tests the onConnect hook
+ */
+ public function testOnConnect()
+ {
+ $time = time() - 1;
+ // We need to make sure time() is going to be creater next time it is called
+
+ $this->plugin->onConnect();
+ $this->assertNull($this->plugin->getLastPing(),
+ 'onConnect should set last ping to null');
+ $this->assertGreaterThan($time,
+ $this->plugin->getLastEvent(),
+ 'onConnect should update lastEvent with the ' .
+ 'current timestamp');
+ $this->assertLessThan($time + 2,
+ $this->plugin->getLastEvent(),
+ 'onConnect should update lastEvent with the ' .
+ 'current timestamp');
+ }
+
+ /**
+ * Test that the preEvent method updates the lastEvent with the current time
+ */
+ public function testPreEvent()
+ {
+ $time = time() -1;
+ $this->plugin->preEvent();
+ $this->assertGreaterThan($time,
+ $this->plugin->getLastEvent(),
+ 'Last event time was set properly on preEvent');
+ $this->assertLessThan($time +2,
+ $this->plugin->getLastEvent(),
+ 'Last Event time was set properly on preEvent');
+ }
+
+ /**
+ * @todo Implement testOnPingResponse().
+ */
+ public function testOnPingResponse()
+ {
+ $this->plugin->setLastPing(time());
+ $this->plugin->onPingResponse();
+ $this->assertNull($this->plugin->getLastPing(),
+ 'Last ping time should be null after onPingResponse');
+
+ }
+
+ /**
+ * Test that the plugin issues a quit when the ping threashold
+ * has been exceeded
+ */
+ public function testOnTickExceededPingThresholdQuits()
+ {
+ $this->plugin->setLastPing(1);
+ $this->plugin->onTick();
+ $this->assertHasEvent(Phergie_Event_Command::TYPE_QUIT);
+ }
+
+ /**
+ * Test that the plugin issues a quit when the ping threashold
+ * has been exceeded
+ */
+ public function testOnTickPingWithinThresholdDoesNotQuits()
+ {
+ $this->plugin->setLastPing(time());
+ $this->plugin->onTick();
+ $this->assertDoesNotHaveEvent(Phergie_Event_Command::TYPE_QUIT);
+ }
+
+ /**
+ * Test that a ping is emitted when the event threashold is exceeded
+ */
+ public function testPingEmittedAfterThresholdExceeded()
+ {
+ $this->plugin->setLastEvent(time() - $this->config['ping.event'] - 1);
+ $this->plugin->onTick();
+ $this->assertHasEvent(Phergie_Event_Command::TYPE_PING);
+ $events = $this->getResponseEvents(Phergie_Event_Command::TYPE_PING);
+ foreach ($events as $event) {
+ $this->assertEventEmitter($event,
+ $this->plugin,
+ 'Assert that the event was emitted by the tested plugin');
+ }
+ }
+
+ /**
+ * Test that no ping is emitted when the event thresthold is not exceeded
+ */
+ public function testNoPingEmittedWhenThresholdNotExceeded()
+ {
+ $this->plugin->setLastEvent(time() - $this->config['ping.event'] +1);
+ $this->plugin->onTick();
+ $this->assertDoesNotHaveEvent(Phergie_Event_Command::TYPE_PING);
+ }
+
+ public function tearDown()
+ {
+ $this->handler->clearEvents();
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?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
+ */
+
+require_once(dirname(__FILE__) . '/TestCase.php');
+
+/**
+ * Unit test suite for Pherge_Plugin_Pong.
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @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_PongTest extends Phergie_Plugin_TestCase
+{
+ /**
+ * Sets up the fixture, for example, opens a network connection.
+ * This method is called before a test is executed.
+ */
+ protected function setUp()
+ {
+ $this->setPlugin(new Phergie_Plugin_Pong);
+ }
+
+ /**
+ * Test that when a ping is received, a Phergie_Event_Command::TYPE_PONG
+ * is set to the handler
+ *
+ * @event Phergie_Event_Command::TYPE_PING
+ */
+ public function testOnPing()
+ {
+ $this->plugin->onPing();
+ $this->assertHasEvent(Phergie_Event_Command::TYPE_PONG);
+ }
+
+ /**
+ * Test that when a ping is received, a Phergie_Event_Command::TYPE_PONG
+ * is set to the handler
+ *
+ * @event Phergie_Event_Command::TYPE_PING
+ */
+ public function testOnPingResponseArguement()
+ {
+ $this->plugin->onPing();
+ $this->assertHasEvent(Phergie_Event_Command::TYPE_PONG);
+ $events = $this->getResponseEvents(Phergie_Event_Command::TYPE_PONG);
+ $this->assertTrue(count($events) === 1, 'Assert that only one pong is emitted');
+ $this->assertEventEmitter(current($events),
+ $this->plugin,
+ 'Assert that the tested plugin emitted the event');
+
+ }
+
+}
--- /dev/null
+<?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
+ */
+
+require_once(dirname(__FILE__) . '/TestCase.php');
+
+/**
+ * Unit test suite for Pherge_Plugin_TerryChay.
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @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_TerryChayTest extends Phergie_Plugin_TestCase
+{
+ /**
+ * Sets up the fixture, for example, opens a network connection.
+ * This method is called before a test is executed.
+ */
+ protected function setUp()
+ {
+ $this->setPlugin(new Phergie_Plugin_TerryChay());
+ $config = new Phergie_Config();
+ $handler = new Phergie_Plugin_Handler($config, $this->handler);
+ $this->plugin->setPluginHandler($handler);
+ $handler->addPlugin($this->plugin);
+ $handler->addPlugin(new Phergie_Plugin_Http($config));
+ $this->plugin->setConfig($config);
+ $this->connection->setNick('phergie');
+ $this->plugin->onLoad();
+ }
+
+ /**
+ * @event Phergie_Event_Request::privmsg
+ * @eventArg #zftalk
+ * @eventArg tychay
+ */
+ public function testWithTyChay()
+ {
+ $this->plugin->onPrivMsg();
+ $this->assertHasEvent(Phergie_Event_Command::TYPE_PRIVMSG);
+ }
+
+ /**
+ * @event Phergie_Event_Request::privmsg
+ * @eventArg #zftalk
+ * @eventArg terrychay
+ */
+ public function testWithTerryChay()
+ {
+ $this->plugin->onPrivMsg();
+ $this->assertDoesNotHaveEvent(Phergie_Event_Command::TYPE_PRIVMSG,
+ 'string "terrychay" should not invoke a response');
+ }
+
+ /**
+ * @event Phergie_Event_Request::privmsg
+ * @eventArg #zftalk
+ * @eventArg terry chay
+ */
+ public function testWithTerry_Chay()
+ {
+ $this->plugin->onPrivMsg();
+ $this->assertHasEvent(Phergie_Event_Command::TYPE_PRIVMSG,
+ 'string "terry chay" should invoke a response');
+ }
+
+ /**
+ * @event Phergie_Event_Request::privmsg
+ * @eventArg #zftalk
+ * @eventArg Elazar is not Mr. Chay
+ */
+ public function testWithNoTyChay()
+ {
+ $this->plugin->onPrivMsg();
+ $this->assertDoesNotHaveEvent(Phergie_Event_Command::TYPE_PRIVMSG,
+ 'Failed asserting that elazar is not ' .
+ 'tychay');
+ }
+}
\ No newline at end of file
--- /dev/null
+<?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_Tests
+ * @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_Tests
+ */
+
+/**
+ * Unit test suite for Pherge_Plugin classes
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+abstract class Phergie_Plugin_TestCase extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @var Phergie_Event_Handler
+ */
+ protected $handler;
+
+ /**
+ * @var Phergie_Connection
+ */
+ protected $connection;
+
+ /**
+ * @var array
+ */
+ protected $eventArgs;
+
+ /**
+ * @var Phergie_Plugin_Abstract
+ */
+ protected $plugin;
+
+ /**
+ * @var array
+ */
+ protected $config = array();
+
+ /**
+ * Constructs a test case with the given name.
+ *
+ * @param string $name
+ * @param array $data
+ * @param string $dataName
+ */
+ public function __construct($name = NULL, array $data = array(), $dataName = '')
+ {
+ parent::__construct($name, $data, $dataName);
+ $this->connection = new Phergie_Connection();
+ $this->handler = new Phergie_Event_Handler();
+ }
+
+ /**
+ * Assert that a given event type exists in the event handler
+ * @param string $event
+ * @param string $message
+ */
+ public function assertHasEvent($event, $message = null)
+ {
+ self::assertTrue($this->handler->hasEventOfType($event), $message);
+ }
+
+ /**
+ * Assert that a given event type DOES NOT exist in the event handler
+ * @param string $event
+ * @param string $message
+ */
+ public function assertDoesNotHaveEvent($event, $message = null)
+ {
+ self::assertFalse($this->handler->hasEventOfType($event), $message);
+ }
+
+ /**
+ * Assert that the emitter of the given command event was the given
+ * plugin
+ *
+ * @param Phergie_Event_Command $event
+ * @param Phergie_Plugin_Abstract $plugin
+ * @param string $message
+ */
+ public function assertEventEmitter(Phergie_Event_Command $event,
+ Phergie_Plugin_Abstract $plugin,
+ $message = null)
+ {
+ $this->assertSame($plugin, $event->getPlugin(), $message);
+ }
+
+ /**
+ * Gets the events added to the handler by the plugin
+ * @param string $type
+ * @return array | null
+ */
+ public function getResponseEvents($type = null)
+ {
+ if (is_string($type) && strlen($type) > 0) {
+ return $this->handler->getEventsOfType($type);
+ }
+ return $this->handler->getEvents();
+ }
+
+ /**
+ * Sets the event for the test
+ * @param array $event
+ * @param array $eventArgs
+ */
+ public function setEvent(array $event, array $eventArgs = null)
+ {
+ $eventClass = 'Phergie_Event_Request';
+ if (is_array($event)) {
+ $eventClass = $event[0];
+ $eventType = $event[1];
+ } else {
+ throw new InvalidArgumentException("Invalid value for \$event");
+ }
+ $event = new $eventClass();
+ $event->setType($eventType);
+ $event->setArguments($eventArgs);
+ $this->plugin->setEvent($event);
+ $this->eventArgs = $eventArgs;
+ }
+
+ /**
+ * Sets the plugin to be tested
+ * If a plugin requries config for testing, an array placed in
+ * $this->config will be parsed into a Phergie_Config object and
+ * attached to the plugin
+ */
+ protected function setPlugin(Phergie_Plugin_Abstract $plugin)
+ {
+ $this->plugin = $plugin;
+ $this->plugin->setEventHandler($this->handler);
+ $this->plugin->setConnection($this->connection);
+ $this->connection->setNick('test');
+ if (!empty($this->config)) {
+ $config = new Phergie_Config();
+ foreach ($this->config as $configKey => $configValue) {
+ $config[$configKey] = $configValue;
+ }
+ $plugin->setConfig($config);
+ }
+ }
+
+ /**
+ * Overrides the runTest method to add additional annotations
+ * @return PHPUnit_Framework_TestResult
+ */
+ protected function runTest()
+ {
+ if (null === $this->plugin) {
+ throw new RuntimeException(
+ 'Tests cannot be run before plugin is set'
+ );
+ }
+
+ // Clean the event handler... important!
+ $this->handler->clearEvents();
+
+ $info = $this->getAnnotations();
+ $event = null;
+ $eventArgs = array();
+ if (isset($info['method']['event']) && isset($info['method']['event'][0])) {
+ if (!is_string($info['method']['event'][0])) {
+ throw new InvalidArgumentException(
+ 'Only one event may be specified'
+ );
+ }
+ $event = $info['method']['event'][0];
+
+ if (stristr($event, '::')) {
+ $event = explode('::', $event);
+ }
+ }
+ if (isset($info['method']['eventArg'])) {
+ $eventArgs = $info['method']['eventArg'];
+ }
+ if (null !== $event) {
+ $this->setEvent($event, $eventArgs);
+ }
+
+ $testResult = parent::runTest();
+
+ // Clean the event handler again... just incase this time.
+ $this->handler->clearEvents();
+
+ return $testResult;
+ }
+
+}
--- /dev/null
+<?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_Tests
+ * @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_Tests
+ */
+
+/**
+ * Creates a plugin on the filesystem that can be used by
+ * Phergie_Plugin_Handler's addPath utility to be located and loaded.
+ *
+ * @category Phergie
+ * @package Phergie_Tests
+ * @author Phergie Development Team <team@phergie.org>
+ * @license http://phergie.org/license New BSD License
+ * @link http://pear.phergie.org/package/Phergie_Tests
+ */
+class Phergie_Plugin_TestNonInstantiablePluginFromFile
+extends Phergie_Plugin_Abstract
+{
+ /**
+ * Private constructor to ensure that this class is not instantiable.
+ *
+ * @return void
+ */
+ private function __construct()
+ {
+ }
+}
--- /dev/null
+<?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_Tests
+ * @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_Tests
+ */
+
+error_reporting(E_ALL | E_STRICT);
+
+// Phergie components require Phergie_Autoload to function correctly.
+require_once dirname(__FILE__) . '/../Phergie/Autoload.php';
+Phergie_Autoload::registerAutoloader();
--- /dev/null
+<!--
+/**
+ * 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_Tests
+ * @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_Tests
+ */
+-->
+<phpunit colors="true" bootstrap="./TestHelper.php">
+ <testsuite name="Phergie Test Suite">
+ <directory>./</directory>
+ </testsuite>
+</phpunit>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project name="Phergie" default="core">
+
+ <tstamp>
+ <format property="DSTAMP" pattern="%Y-%m-%d" />
+ </tstamp>
+
+ <taskdef name="phergiepkg" classname="PhergiePackageTask" />
+ <taskdef name="phpdocumentor" classname="phing.tasks.ext.phpdoc.PhpDocumentorTask" />
+
+ <available file="./build.properties" property="have_properties_file" />
+
+ <property name="clean" value="true" />
+
+ <target name="input">
+
+ <if>
+ <equals arg1="${have_properties_file}" arg2="true" />
+ <then>
+ <property file="./build.properties" />
+ </then>
+ <else>
+ <input propertyname="build.srcdir" defaultvalue="./" message="Source directory" />
+ <input propertyname="build.dstdir" defaultvalue="./" message="Destination directory" />
+ <input propertyname="build.version.release" message="Release version" />
+ <input propertyname="build.version.api" message="API version" />
+ <input propertyname="build.stability.release" defaultvalue="stable" message="Release stability" validArgs="snapshot,devel,alpha,beta,stable" />
+ <input propertyname="build.stability.api" defaultvalue="stable" message="API stability" validArgs="snapshot,devel,alpha,beta,stable" />
+ <input propertyname="build.notes" message="Release notes" />
+ <input propertyname="build.phpdep" defaultvalue="5.2.0" message="PHP version required" />
+ <input propertyname="build.pearinstallerdep" defaultvalue="1.9.0" message="PEAR installer version required" />
+ </else>
+ </if>
+
+ <fileset dir="${build.srcdir}" id="core">
+ <include name="phergie.php" />
+ <include name="phergie.bat" />
+ <include name="LICENSE" />
+ <include name="Settings.php.dist" />
+ <include name="Phergie/Autoload.php" />
+ <include name="Phergie/Bot.php" />
+ <include name="Phergie/Config/Exception.php" />
+ <include name="Phergie/Config.php" />
+ <include name="Phergie/Connection/Exception.php" />
+ <include name="Phergie/Connection/Handler.php" />
+ <include name="Phergie/Connection.php" />
+ <include name="Phergie/Driver/Abstract.php" />
+ <include name="Phergie/Driver/Exception.php" />
+ <include name="Phergie/Driver/Streams.php" />
+ <include name="Phergie/Event/Abstract.php" />
+ <include name="Phergie/Event/Command.php" />
+ <include name="Phergie/Event/Exception.php" />
+ <include name="Phergie/Event/Handler.php" />
+ <include name="Phergie/Event/Request.php" />
+ <include name="Phergie/Event/Response.php" />
+ <include name="Phergie/Exception.php" />
+ <include name="Phergie/Hostmask/Exception.php" />
+ <include name="Phergie/Hostmask.php" />
+ <include name="Phergie/Plugin/Abstract.php" />
+ <include name="Phergie/Plugin/Exception.php" />
+ <include name="Phergie/Plugin/Handler.php" />
+ <include name="Phergie/Ui/Abstract.php" />
+ <include name="Phergie/Ui/Console.php" />
+ </fileset>
+
+ </target>
+
+ <target name="core" depends="input">
+
+ <property name="build.tmpdir" value="Phergie-${build.version.release}" />
+ <property name="build.tarball" value="${build.dstdir}${build.tmpdir}.tgz" />
+
+ <delete file="${build.tarball}" quiet="true" />
+
+ <mkdir dir="${build.tmpdir}" />
+
+ <copy todir="${build.tmpdir}">
+ <fileset refid="core" />
+ </copy>
+
+ <reflexive file="${build.tmpdir}/Phergie/Bot.php">
+ <filterchain>
+ <replaceregexp>
+ <regexp
+ pattern="const VERSION = '[^']+';"
+ replace="const VERSION = '${build.version.release}';"
+ />
+ </replaceregexp>
+ </filterchain>
+ </reflexive>
+
+ <phergiepkg name="Phergie" dir="${build.tmpdir}">
+ <fileset refid="core" />
+ <option name="baseinstalldir" value="/" />
+ <option name="outputdirectory" value="${build.dstdir}" />
+ <option name="channel" value="pear.phergie.org" />
+ <option name="summary" value="Phergie core library" />
+ <option name="description" value="The Phergie package provides all files necessary to run a basic IRC bot." />
+ <option name="apiversion" value="${build.version.api}" />
+ <option name="apistability" value="${build.stability.api}" />
+ <option name="releaseversion" value="${build.version.release}" />
+ <option name="releasestability" value="${build.stability.release}" />
+ <option name="phpdep" value="${build.phpdep}" />
+ <option name="pearinstallerdep" value="${build.pearinstallerdep}" />
+ <option name="license" value="http://phergie.org/license New BSD License" />
+ <option name="packagetype" value="php" />
+ <option name="notes" value="${build.notes}" />
+ <mapping name="replacements">
+ <element>
+ <element key="path" value="phergie.php" />
+ <element key="type" value="pear-config" />
+ <element key="from" value="/usr/bin/env php" />
+ <element key="to" value="php_bin" />
+ </element>
+ <element>
+ <element key="path" value="phergie.bat" />
+ <element key="type" value="pear-config" />
+ <element key="from" value="@php_bin@" />
+ <element key="to" value="php_bin" />
+ </element>
+ <element>
+ <element key="path" value="phergie.bat" />
+ <element key="type" value="pear-config" />
+ <element key="from" value="@bin_dir@" />
+ <element key="to" value="bin_dir" />
+ </element>
+ </mapping>
+ <mapping name="exceptions">
+ <element key="phergie.php" value="script" />
+ <element key="phergie.bat" value="script" />
+ </mapping>
+ <mapping name="releases">
+ <element>
+ <element key="installconditions">
+ <element key="os" value="windows" />
+ </element>
+ <element key="filelist">
+ <element key="install">
+ <element key="phergie.php" value="phergie" />
+ </element>
+ </element>
+ </element>
+ <element>
+ <element key="filelist">
+ <element key="install">
+ <element key="phergie.php" value="phergie" />
+ </element>
+ <element key="ignore">
+ <element value="phergie.bat" />
+ </element>
+ </element>
+ </element>
+ </mapping>
+ <mapping name="deps">
+ <element>
+ <element key="type" value="ext" />
+ <element key="name" value="pcre" />
+ </element>
+ <element>
+ <element key="type" value="ext" />
+ <element key="name" value="reflection" />
+ </element>
+ </mapping>
+ </phergiepkg>
+
+ <phingcall target="build" />
+
+ <phingcall target="clean" />
+
+ </target>
+
+ <target name="plugin" depends="input">
+
+ <if>
+ <equals arg1="${have_properties_file}" arg2="true" />
+ <then>
+ <property file="./build.properties" />
+ </then>
+ <else>
+ <input propertyname="build.plugin" message="Short plugin name" />
+ <input propertyname="build.summary" message="Plugin summary" />
+ <input propertyname="build.description" message="Plugin description" />
+ </else>
+ </if>
+
+ <property name="build.class" value="Phergie_Plugin_${build.plugin}" />
+ <property name="build.tmpdir" value="${build.class}-${build.version.release}" />
+ <property name="build.tarball" value="${build.dstdir}${build.tmpdir}.tgz" />
+
+ <fileset dir="${build.srcdir}" id="plugin">
+ <include name="Phergie/Plugin/${build.plugin}.php" />
+ <include name="Phergie/Plugin/${build.plugin}/**" />
+ </fileset>
+
+ <delete file="${build.tarball}" quiet="true" />
+
+ <mkdir dir="${build.tmpdir}" />
+
+ <copy todir="${build.tmpdir}">
+ <fileset refid="plugin" />
+ </copy>
+
+ <phergiepkg name="${build.class}" dir="${build.tmpdir}">
+ <fileset refid="plugin" />
+ <option name="baseinstalldir" value="/" />
+ <option name="outputdirectory" value="${build.dstdir}" />
+ <option name="channel" value="pear.phergie.org"/>
+ <option name="summary" value="${build.summary}"/>
+ <option name="description" value="${build.description}"/>
+ <option name="apiversion" value="${build.version.api}"/>
+ <option name="apistability" value="${build.stability.api}"/>
+ <option name="releaseversion" value="${build.version.release}"/>
+ <option name="releasestability" value="${build.stability.release}"/>
+ <option name="phpdep" value="${build.phpdep}" />
+ <option name="pearinstallerdep" value="${build.pearinstallerdep}" />
+ <option name="license" value="http://phergie.org/license New BSD License"/>
+ <option name="packagetype" value="php"/>
+ <option name="notes" value="${build.notes}"/>
+ </phergiepkg>
+
+ <phingcall target="build" />
+
+ <phingcall target="clean" />
+
+ </target>
+
+ <target name="docs" depends="input">
+
+ <property name="build.tmpdir" value="Phergie_Docs-${build.version.release}" />
+ <property name="build.tarball" value="${build.dstdir}${build.tmpdir}.tgz" />
+
+ <delete file="${build.tarball}" quiet="true" />
+
+ <mkdir dir="${build.tmpdir}" />
+
+ <phpdocumentor title="API Documentation"
+ destdir="${build.tmpdir}/api"
+ output="HTML:Smarty:PHP">
+ <fileset refid="core" />
+ <projdocfileset dir=".">
+ <include name="LICENSE" />
+ </projdocfileset>
+ </phpdocumentor>
+
+ <phergiepkg name="Phergie_Docs" dir="${build.tmpdir}">
+ <fileset dir="${build.tmpdir}">
+ <include name="api**" />
+ </fileset>
+ <option name="baseinstalldir" value="/" />
+ <option name="outputdirectory" value="${build.dstdir}" />
+ <option name="channel" value="pear.phergie.org" />
+ <option name="summary" value="Phergie core library documentation" />
+ <option name="description" value="The Phergie_Docs package provides documentation for the Phergie core libraries." />
+ <option name="apiversion" value="${build.version.api}" />
+ <option name="apistability" value="${build.stability.api}" />
+ <option name="releaseversion" value="${build.version.release}" />
+ <option name="releasestability" value="${build.stability.release}" />
+ <option name="phpdep" value="${build.phpdep}" />
+ <option name="pearinstallerdep" value="${build.pearinstallerdep}" />
+ <option name="license" value="http://phergie.org/license New BSD License" />
+ <option name="packagetype" value="php" />
+ <option name="notes" value="${build.notes}" />
+ <mapping name="exceptions">
+ <element key="api" value="doc" />
+ </mapping>
+ </phergiepkg>
+
+ <phingcall target="build" />
+
+ <phingcall target="clean" />
+
+ </target>
+
+ <target name="build">
+
+ <tar destfile="${build.tarball}" compression="gzip">
+ <fileset dir="${build.dstdir}">
+ <include name="${build.tmpdir}**" />
+ <include name="package.xml" />
+ </fileset>
+ </tar>
+
+ </target>
+
+ <target name="clean">
+
+ <if>
+ <istrue value="${clean}" />
+ <then>
+ <delete dir="${build.tmpdir}" />
+ <delete file="${build.dstdir}package.xml" />
+ </then>
+ </if>
+
+ </target>
+
+</project>
--- /dev/null
+@echo off
+REM Phergie
+REM
+REM PHP version 5
+REM
+REM LICENSE
+REM
+REM This source file is subject to the new BSD license that is bundled
+REM with this package in the file LICENSE.
+REM It is also available through the world-wide-web at this URL:
+REM http://phergie.org/license
+
+set PHPBIN="@php_bin@"
+%PHPBIN% "@bin_dir@\phergie" %*
--- /dev/null
+#!/usr/bin/env php
+<?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
+ */
+
+/**
+ * @see Phergie_Autoload
+ */
+require 'Phergie/Autoload.php';
+Phergie_Autoload::registerAutoloader();
+
+$bot = new Phergie_Bot;
+
+if (!isset($argc)) {
+ echo
+ 'The PHP setting register_argc_argv must be enabled for Phergie ',
+ 'configuration files to be specified using command line arguments; ',
+ 'defaulting to Settings.php in the current working directory',
+ PHP_EOL;
+} else if ($argc > 0) {
+ // Skip the current file for manual installations
+ // ex: php phergie.php Settings.php
+ if (realpath($argv[0]) == __FILE__) {
+ array_shift($argv);
+ }
+
+ // If configuration files were specified, override default behavior
+ if (count($argv) > 0) {
+ $config = new Phergie_Config;
+ foreach ($argv as $file) {
+ $config->read($file);
+ }
+ $bot->setConfig($config);
+ }
+}
+
+$bot->run();