diff options
Diffstat (limited to 'phpBB/plugins/bootstrap.php')
-rw-r--r-- | phpBB/plugins/bootstrap.php | 550 |
1 files changed, 404 insertions, 146 deletions
diff --git a/phpBB/plugins/bootstrap.php b/phpBB/plugins/bootstrap.php index 7db4eec96c..c3c3d4817d 100644 --- a/phpBB/plugins/bootstrap.php +++ b/phpBB/plugins/bootstrap.php @@ -1,249 +1,507 @@ <?php /** * -* @package phpBB3 +* @package plugins * @version $Id$ -* @copyright (c) 2007 phpBB Group +* @copyright (c) 2008 phpBB Group * @license http://opensource.org/licenses/gpl-license.php GNU Public License * */ /** -* @ignore +* Class used by plugins/modules/etc. for installation/uninstallation */ -if (!defined('IN_PHPBB')) +class phpbb_install { - exit; + public function install() {} + public function uninstall() {} } /** -* phpBB Hook Class -* @package phpBB3 +* Main class handling plugins/hooks */ -class phpbb_hook +class phpbb_plugins { - /** - * Registered hooks - */ - var $hooks = array(); + public $phpbb_required = array(); + public $phpbb_optional = array(); - /** - * Results returned by functions called - */ - var $hook_result = array(); + public $plugin_path = false; + public $plugins = array(); - /** - * internal pointer - */ - var $current_hook = NULL; + private $hooks = array(); + private $current_plugin = false; /** - * Initialize hook class. * - * @param array $valid_hooks array containing the hookable functions/methods + * @return + * @param string $plugin_path */ - function __construct($valid_hooks) + public function init($plugin_path) { - foreach ($valid_hooks as $_null => $method) - { - $this->add_hook($method); - } + $this->plugin_path = $plugin_path; + $this->plugins = array(); - if (function_exists('phpbb_hook_register')) + // search for plugin files + if ($dh = @opendir($this->plugin_path)) { - phpbb_hook_register($this); + while (($file = readdir($dh)) !== false) + { + // If is directory and a PHP file with the same name as the directory within this dir? + if ($file[0] != '.' && is_readable($this->plugin_path . $file) && is_dir($this->plugin_path . $file) && file_exists($this->plugin_path . $file . '/' . $file . '.' . PHP_EXT)) + { + $this->add_plugin($file); + } + } + closedir($dh); } } /** - * Register function/method to be called within hook - * This function is normally called by the modification/application to attach/register the functions. * - * @param mixed $definition Declaring function (with __FUNCTION__) or class with array(__CLASS__, __FUNCTION__) - * @param mixed $hook The replacement function/method to be called. Passing function name or array with object/class definition - * @param string $mode Specify the priority/chain mode. 'normal' -> hook gets appended to the chain. 'standalone' -> only the specified hook gets called - later hooks are not able to overwrite this (E_NOTICE is triggered then). 'first' -> hook is called as the first one within the chain. 'last' -> hook is called as the last one within the chain. + * @return + * @param string $phpbb_name */ - function register($definition, $hook, $mode = 'normal') + public function add_plugin($phpbb_name) { - $class = (!is_array($definition)) ? '__global' : $definition[0]; - $function = (!is_array($definition)) ? $definition : $definition[1]; - - // Method able to be hooked? - if (isset($this->hooks[$class][$function])) + if (!file_exists($this->plugin_path . $phpbb_name . '/' . $phpbb_name . '.' . PHP_EXT)) { - switch ($mode) - { - case 'standalone': - if (!isset($this->hooks[$class][$function]['standalone'])) - { - $this->hooks[$class][$function] = array('standalone' => $hook); - } - else - { - trigger_error('Hook not able to be called standalone, previous hook already standalone.', E_NOTICE); - } - break; + return false; + } - case 'first': - case 'last': - $this->hooks[$class][$function][$mode][] = $hook; - break; + // Include desired plugin + require_once $this->plugin_path . $phpbb_name . '/' . $phpbb_name . '.' . PHP_EXT; - case 'normal': - default: - $this->hooks[$class][$function]['normal'][] = $hook; - break; - } - } + // Create new setup for this plugin + $this->plugins[$phpbb_name] = new phpbb_plugin_structure($phpbb_name); + $this->current_plugin = $this->plugins[$phpbb_name]; + + // Setup plugin + $this->current_plugin->setup->setup_plugin($this); } - /** - * Calling all functions/methods attached to a specified hook. - * Called by the function allowing hooks... - * - * @param mixed $definition Declaring function (with __FUNCTION__) or class with array(__CLASS__, __FUNCTION__) - * @return bool False if no hook got executed, true otherwise - */ - function call_hook($definition) + public function setup() { - $class = (!is_array($definition)) ? '__global' : $definition[0]; - $function = (!is_array($definition)) ? $definition : $definition[1]; + if (empty($this->plugins)) + { + return false; + } - if (!empty($this->hooks[$class][$function])) + foreach ($this->plugins as $name => $plugin) { - // Developer tries to call a hooked function within the hooked function... - if ($this->current_hook !== NULL && $this->current_hook['class'] === $class && $this->current_hook['function'] === $function) + // Add includes + foreach ($plugin->includes as $file) { - return false; + include_once $this->plugin_path . $name . '/' . $file . '.' . PHP_EXT; } - // Call the hook with the arguments attached and store result - $arguments = func_get_args(); - $this->current_hook = array('class' => $class, 'function' => $function); - $arguments[0] = &$this; - - // Call the hook chain... - if (isset($this->hooks[$class][$function]['standalone'])) - { - $this->hook_result[$class][$function] = call_user_func_array($this->hooks[$class][$function]['standalone'], $arguments); - } - else + // Setup objects + foreach ($plugin->objects as $key => $class) { - foreach (array('first', 'normal', 'last') as $mode) + $object = new $class(); + + if (!property_exists($object, 'phpbb_plugin') && !property_exists($object, 'class_plugin')) + { + trigger_error('Class ' . get_class($object) . ' does not define public $phpbb_plugin and public $class_plugin.', E_USER_ERROR); + } + + if (property_exists($object, 'phpbb_plugin') && !empty($object->phpbb_plugin)) { - if (!isset($this->hooks[$class][$function][$mode])) + // Is the plugin the mod author wants to influence pluggable? + if (!is_subclass_of(phpbb::get_instance($object->phpbb_plugin), 'phpbb_plugin_support')) { - continue; + trigger_error('The phpBB Class ' . get_class(phpbb::get_instance($object->phpbb_plugin)) . ' defined in ' . get_class($object) . ' is not pluggable.', E_USER_ERROR); } - foreach ($this->hooks[$class][$function][$mode] as $hook) + $instance = phpbb::get_instance($object->phpbb_plugin); + } + else + { + $instance = ${$object->object_plugin}; + + if (!is_subclass_of($instance, 'phpbb_plugin_support')) { - $this->hook_result[$class][$function] = call_user_func_array($hook, $arguments); + trigger_error('The Class ' . get_class($instance) . ' defined in ' . get_class($object) . ' is not pluggable.', E_USER_ERROR); } } + + // Setup/Register plugin... + $object->setup_plugin($instance); +// $plugin->objects[$key] = $object; + } + + // Now setup the functions... this is a special case... + foreach ($plugin->functions as $params) + { + $function = array_shift($params); + $hook = array_shift($params); + $mode = (!empty($params)) ? array_shift($params) : phpbb::FUNCTION_INJECT; + $action = (!empty($params)) ? array_shift($params) : 'default'; + + // Check if the function is already overridden. + if ($mode == phpbb::FUNCTION_OVERRIDE && isset($this->hooks[$function][$mode])) + { + trigger_error('Function ' . $function . ' is already overwriten by ' . $this->hooks[$function][$mode] . '.', E_USER_ERROR); + } + + if ($mode == phpbb::FUNCTION_OVERRIDE) + { + $this->hooks[$function][$mode] = $hook; + } + else + { + $this->hooks[$function][$mode][$action][] = $hook; + } } - $this->current_hook = NULL; - return true; + // Call init method? + if (method_exists($plugin->setup, 'init')) + { + $plugin->setup->init(); + } } + } + + public function register_includes() + { + $arguments = func_get_args(); + $this->current_plugin->includes = $arguments; + } + + public function register_plugins() + { + $arguments = func_get_args(); + $this->current_plugin->objects = $arguments; + } + + public function register_function() + { + $arguments = func_get_args(); + $this->current_plugin->functions[] = $arguments; + } + + public function function_override($function) + { + return isset($this->hooks[$function][phpbb::FUNCTION_OVERRIDE]); + } + + public function function_inject($function, $action = 'default') + { + return isset($this->hooks[$function][phpbb::FUNCTION_INJECT][$action]); + } + + public function call_override() + { + $arguments = func_get_args(); + $function = array_shift($arguments); - $this->current_hook = NULL; - return false; + return call_user_func_array($this->hooks[$function][phpbb::FUNCTION_OVERRIDE], $arguments); } /** - * Get result from previously called functions/methods for the same hook + * Call injected function. + * + * Arguments are layed out in the following way: + * action: The action: + * 'default': If $action is default, then the hook is called in the beginning, original parameter passed by reference + * 'return': If $action is return, then the hook is called at the end and the result will be returned. The hook expects the $result as the first parameter, all other parameters passed by name + * If $action is not default and not return it could be a custom string. Please refer to the plugin documentation to determine possible combinations. Parameters are passed by reference. * - * @param mixed $definition Declaring function (with __FUNCTION__) or class with array(__CLASS__, __FUNCTION__) - * @return mixed False if nothing returned if there is no result, else array('result' => ... ) + * @param string $function Original function name this method is called from + * @param array $arguments Arguments */ - function previous_hook_result($definition) + public function call_inject($function, $arguments) { - $class = (!is_array($definition)) ? '__global' : $definition[0]; - $function = (!is_array($definition)) ? $definition : $definition[1]; + $result = NULL; - if (!empty($this->hooks[$class][$function]) && isset($this->hook_result[$class][$function])) + if (!is_array($arguments)) { - return array('result' => $this->hook_result[$class][$function]); + $action = $arguments; + $arguments = array(); } + else + { + $action = array_shift($arguments); + } + + // Return action... handle like override + if ($action == 'return') + { + $result = array_shift($arguments); + + foreach ($this->hooks[$function][phpbb::FUNCTION_INJECT][$action] as $key => $hook) + { + $args = array_merge(array($result), $arguments); + $result = call_user_func_array($hook, $args); + } - return false; + return $result; + } + + foreach ($this->hooks[$function][phpbb::FUNCTION_INJECT][$action] as $key => $hook) + { + call_user_func_array($hook, $arguments); + } } +} + +// Object used to hold plugin information. Per plugin one instance +class phpbb_plugin_structure +{ + public $phpbb_name; + public $name; + public $description; + public $author; + public $version; + + public $includes = array(); + public $objects = array(); + public $functions = array(); /** - * Check if the called functions/methods returned something. * - * @param mixed $definition Declaring function (with __FUNCTION__) or class with array(__CLASS__, __FUNCTION__) - * @return bool True if results are there, false if not + * @return + * @param string $phpbb_name */ - function hook_return($definition) + public function __construct($phpbb_name) + { + $this->phpbb_name = $phpbb_name; + + $class = 'phpbb_' . $phpbb_name . '_info'; + $this->setup = new $class(); + + foreach (array('name', 'description', 'author', 'version') as $required_property) + { + $this->$required_property = $this->setup->$required_property; + } + } +} + +interface phpbb_plugin_info +{ + public function setup_plugin(phpbb_plugins $object); +} + +interface phpbb_plugin_setup +{ + function setup_plugin(phpbb_plugin_support $object); +} + + +abstract class phpbb_plugin_support +{ + private $plugin_methods; + private $plugin_attributes; + + public function register_method($name, $method, $object, $mode = phpbb::PLUGIN_ADD, $action = 'default') { - $class = (!is_array($definition)) ? '__global' : $definition[0]; - $function = (!is_array($definition)) ? $definition : $definition[1]; + // Method reachable by: + // For plugin_add: plugin_methods[method] = object + // For plugin_override: plugin_methods[name][mode][method] = object + // For plugin_inject: plugin_methods[name][mode][action][method] = object + + // Set to PLUGIN_ADD if method does not exist + if ($name === false || !method_exists($this, $name)) + { + $mode = phpbb::PLUGIN_ADD; + } - if (!empty($this->hooks[$class][$function]) && isset($this->hook_result[$class][$function])) + // But if it exists and we try to add one, then print out an error + if ($mode == phpbb::PLUGIN_ADD && (method_exists($this, $method) || isset($this->plugin_methods[$method]))) { - return true; + trigger_error('Method ' . $method. ' in class ' . get_class($object) . ' is not able to be added, because it conflicts with the existing method ' . $method . ' in ' . get_class($this) . '.', E_USER_ERROR); } - return false; + // Check if the same method name is already used for $name for overriding the method. + if ($mode == phpbb::PLUGIN_OVERRIDE && isset($this->plugin_methods[$name][$mode][$method])) + { + trigger_error('Method ' . $method . ' in class ' . get_class($object) . ' is not able to override . ' . $name . ' in ' . get_class($this) . ', because it is already overridden in ' . get_class($this->plugin_methods[$name][$mode][$method]) . '.', E_USER_ERROR); + } + + // Check if another method is already defined... + if ($mode == phpbb::PLUGIN_INJECT && isset($this->plugin_methods[$name][$mode][$action][$method])) + { + trigger_error('Method ' . $method . ' in class ' . get_class($object) . ' for ' . $name . ' is already defined in class ' . get_class($this->plugin_methods[$name][$mode][$action][$method]), E_USER_ERROR); + } + + if (($function_signature = $this->valid_parameter($object, $method, $mode, $action)) !== true) + { + trigger_error('Method ' . $method . ' in class ' . get_class($object) . ' has invalid function signature. Please use: ' . $function_signature, E_USER_ERROR); + } + + if ($mode == phpbb::PLUGIN_ADD) + { + $this->plugin_methods[$method] = $object; + } + else if ($mode == phpbb::PLUGIN_OVERRIDE) + { + $this->plugin_methods[$name][$mode][$method] = $object; + } + else + { + $this->plugin_methods[$name][$mode][$action][$method] = $object; + } } - /** - * Give actual result from called functions/methods back. - * - * @param mixed $definition Declaring function (with __FUNCTION__) or class with array(__CLASS__, __FUNCTION__) - * @return mixed The result - */ - function hook_return_result($definition) + public function register_attribute($name, $object) { - $class = (!is_array($definition)) ? '__global' : $definition[0]; - $function = (!is_array($definition)) ? $definition : $definition[1]; + if (property_exists($this, $name)) + { + unset($this->$name); + } - if (!empty($this->hooks[$class][$function]) && isset($this->hook_result[$class][$function])) + if (isset($this->plugin_attributes[$name])) { - $result = $this->hook_result[$class][$function]; - unset($this->hook_result[$class][$function]); - return $result; + trigger_error('Attribute ' . $name . ' in class ' . get_class($object) . ' already defined in class ' . get_class($this->plugin_attributes[$name]), E_USER_ERROR); } - return; + $this->plugin_attributes[$name] = $object; + } + + protected function method_override($name) + { + return isset($this->plugin_methods[$name][phpbb::PLUGIN_OVERRIDE]); + } + + protected function method_inject($name, $action = 'default') + { + return isset($this->plugin_methods[$name][phpbb::PLUGIN_INJECT][$action]); + } + + public function call_override() + { + $arguments = func_get_args(); + $name = array_shift($arguments); + + list($method, $object) = each($this->plugin_methods[$name][phpbb::PLUGIN_OVERRIDE]); + return call_user_func_array(array($object, $method), array_merge(array($this), $arguments)); } /** - * Add new function to the allowed hooks. + * Call injected method. + * + * Arguments are layed out in the following way: + * action: The action: + * 'default': If $action is default, then the plugin is called in the beginning, original parameter passed by reference + * 'return': If $action is return, then the plugin is called at the end and the result will be returned. The plugin expects the $result as the first parameter, all other parameters passed by name + * If $action is not default and not return it could be a custom string. Please refer to the plugin documentation to determine possible combinations. Parameters are passed by reference. * - * @param mixed $definition Declaring function (with __FUNCTION__) or class with array(__CLASS__, __FUNCTION__) + * @param string $name Original method name this method is called from + * @param array $arguments Arguments */ - function add_hook($definition) + public function call_inject($name, $arguments) { - if (!is_array($definition)) + $result = NULL; + + if (!is_array($arguments)) + { + $action = $arguments; + $arguments = array(); + } + else { - $definition = array('__global', $definition); + $action = array_shift($arguments); } - $this->hooks[$definition[0]][$definition[1]] = array(); + // Return action... handle like override + if ($action == 'return') + { + $result = array_shift($arguments); + + foreach ($this->plugin_methods[$name][phpbb::PLUGIN_INJECT][$action] as $method => $object) + { + $args = array_merge(array($this, $result), $arguments); + $result = call_user_func_array(array($object, $method), $args); + } + + return $result; + } + + foreach ($this->plugin_methods[$name][phpbb::PLUGIN_INJECT][$action] as $method => $object) + { + call_user_func_array(array($object, $method), array_merge(array($this), $arguments)); + } } - /** - * Remove function from the allowed hooks. - * - * @param mixed $definition Declaring function (with __FUNCTION__) or class with array(__CLASS__, __FUNCTION__) - */ - function remove_hook($definition) + // Getter/Setter + public function __get($name) + { + return $this->plugin_attributes[$name]->$name; + } + + public function __set($name, $value) + { + return $this->plugin_attributes[$name]->$name = $value; + } + + public function __isset($name) + { + return isset($this->plugin_attributes[$name]->$name); + } + + public function __unset($name) { - $class = (!is_array($definition)) ? '__global' : $definition[0]; - $function = (!is_array($definition)) ? $definition : $definition[1]; + unset($this->plugin_attributes[$name]->$name); + } - if (isset($this->hooks[$class][$function])) + public function __call($name, $arguments) + { + array_unshift($arguments, $this); + return call_user_func_array(array($this->plugin_methods[$name], $name), $arguments); + } + + private function valid_parameter($object, $method, $mode, $action) + { + // We cache the results... no worry. These checks are quite resource intensive, but will hopefully educate and guide developers + + // Check for correct first parameter. This must be an instance of phpbb_$phpbb_plugin + $instance_of = 'phpbb_' . $object->phpbb_plugin; + + // Define the required function layout + $function_layout = 'public function ' . $method . '(' . $instance_of . ' $object'; + + // Result for PLUGIN_INJECT and action == 'return' + if ($mode == phpbb::PLUGIN_INJECT && $action == 'return') { - unset($this->hooks[$class][$function]); + $function_layout .= ', $result'; + } + + $function_layout .= ', [...]) { [...] }'; + + $reflection = new ReflectionMethod($object, $method); + $parameters = $reflection->getParameters(); + $first_param = array_shift($parameters); + + // Try to get class + if (empty($first_param)) + { + return $function_layout; + } - if (isset($this->hook_result[$class][$function])) + try + { + $first_param->getClass()->name; + } + catch (Exception $e) + { + return $function_layout; + } + + if ($first_param->getClass()->name !== $instance_of || $first_param->getName() !== 'object') + { + return $function_layout; + } + + if ($mode == phpbb::PLUGIN_INJECT && $action == 'return') + { + $first_param = array_shift($parameters); + + if (empty($first_param) || $first_param->getName() !== 'result' || $first_param->isOptional()) { - unset($this->hook_result[$class][$function]); + return $function_layout; } } + + return true; } } |