forked from GNUsocial/gnu-social
413 lines
12 KiB
PHP
413 lines
12 KiB
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
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* 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;
|
||
|
}
|
||
|
}
|