add some properties
[phpbb.git] / phpBB / plugins / bootstrap.php
blob443624bbf93421beec55067191d90754fedd6d57
1 <?php
2 /**
4 * @package plugins
5 * @version $Id$
6 * @copyright (c) 2008 phpBB Group
7 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
9 */
11 /**
12 * Class used by plugins/modules/etc. for installation/uninstallation
13 * @package plugins
15 class phpbb_install
17 public function install() {}
18 public function uninstall() {}
21 /**
22 * This class holds plugin information.
23 * One instance will be assigned to each plugin.
24 * @package plugins
26 class phpbb_plugin_structure
28 /**
29 * @var string The plugins plain phpBB name. This is also the directory name.
31 public $phpbb_name;
33 /**
34 * @var string The plugins full name as the plugin defines it
36 public $name;
38 /**
39 * @var string The plugins full description as the plugin defines it
41 public $description;
43 /**
44 * @var string The plugins author
46 public $author;
48 /**
49 * @var string The plugins version string
51 public $version;
53 /**
54 * @var array The plugins defined includes
56 public $includes = array();
58 /**
59 * @var array The plugins defined objects to plug in
61 public $objects = array();
63 /**
64 * @var array The plugins defined hooks/functions to plug in
66 public $functions = array();
68 /**
69 * Set up information object
71 * @param string $phpbb_name The plugins directory/simple/plain name
72 * @access public
74 public function __construct($phpbb_name)
76 $this->phpbb_name = $phpbb_name;
78 $class = 'phpbb_' . $phpbb_name . '_info';
80 if (!class_exists($class))
82 trigger_error('Plugin ' . $phpbb_name . ' does not define required ' . $class . ' info class.', E_USER_ERROR);
85 $this->setup = new $class();
87 foreach (array('name', 'description', 'author', 'version') as $required_property)
89 if (!property_exists($this->setup, $required_property))
91 trigger_error('Plugin ' . $phpbb_name . ' does not define required property ' . $required_property . ' in info class ' . $class . '.', E_USER_ERROR);
94 $this->$required_property = $this->setup->$required_property;
99 /**
100 * Main class handling plugins/hooks
101 * @package plugins
103 class phpbb_plugins
106 * @var array required phpBB objects
108 public $phpbb_required = array();
111 * @var array Optional phpBB objects
113 public $phpbb_optional = array();
116 * @var string Plugin path plugins are searched at.
118 public $plugin_path = false;
121 * @var array Collection of assigned plugins
123 private $plugins = array();
126 * @var array Collection of assigned hooks (functions)
128 private $hooks = array();
131 * Pointer to currently processed plugin.
133 private $current_plugin = false;
136 * Plugins/Functions already set up
138 private $already_parsed_plugins = array();
141 * @var array Collection of assigned functions and their params (merged from plugins and custom additions)
143 private $functions = array();
146 * Init plugins
148 * Search {@link $plugin_path plugin path} for plugins and include the information for later processing.
149 * Plugins will not be called here, just collected.
151 * @param string $plugin_path The plugin path to search.
152 * @access public
154 public function init($plugin_path)
156 $this->plugin_path = $plugin_path;
157 $this->plugins = array();
159 // search for plugin files
160 if ($dh = @opendir($this->plugin_path))
162 while (($file = readdir($dh)) !== false)
164 // If is directory and a PHP file with the same name as the directory within this dir?
165 if ($file[0] != '.' && is_readable($this->plugin_path . $file) && is_dir($this->plugin_path . $file) && file_exists($this->plugin_path . $file . '/' . $file . '.' . PHP_EXT))
167 $this->add_plugin($file);
170 closedir($dh);
173 $this->already_parsed_plugins = array('plugin' => array(), 'hook' => array());
177 * Add information about specific plugin
179 * @param string $name Plugin directory name
180 * @return bool False if plugin does not exist
181 * @access public
183 public function add_plugin($name)
185 if (!file_exists($this->plugin_path . $name . '/' . $name . '.' . PHP_EXT))
187 return false;
190 // Include desired plugin
191 require_once $this->plugin_path . $name . '/' . $name . '.' . PHP_EXT;
193 // Create new setup for this plugin
194 $this->plugins[$name] = new phpbb_plugin_structure($name);
195 $this->current_plugin = $this->plugins[$name];
197 // Setup plugin
198 $this->current_plugin->setup->setup_plugin($this);
200 return true;
204 * Setup all previously collected plugins
206 * @return bool False if there are no plugins to setup
207 * @access public
209 public function setup()
211 foreach ($this->plugins as $name => $plugin)
213 if (isset($this->already_parsed_plugins['plugin'][$name]))
215 continue;
218 // Add includes
219 foreach ($plugin->includes as $file)
221 include_once $this->plugin_path . $name . '/' . $file . '.' . PHP_EXT;
224 // Setup objects
225 foreach ($plugin->objects as $key => $class)
227 $object = new $class();
229 if (!property_exists($object, 'phpbb_plugin') && !property_exists($object, 'class_plugin'))
231 trigger_error('Class ' . get_class($object) . ' does not define public $phpbb_plugin and public $class_plugin.', E_USER_ERROR);
234 if (property_exists($object, 'phpbb_plugin') && !empty($object->phpbb_plugin))
236 // Is the plugin the mod author wants to influence pluggable?
237 if (!is_subclass_of(phpbb::get_instance($object->phpbb_plugin), 'phpbb_plugin_support'))
239 trigger_error('The phpBB Class ' . get_class(phpbb::get_instance($object->phpbb_plugin)) . ' defined in ' . get_class($object) . ' is not pluggable.', E_USER_ERROR);
242 // Get instance of phpBB Object to pass later
243 $instance = phpbb::get_instance($object->phpbb_plugin);
245 else
247 // Assign custom object...
248 $instance = ${$object->class_plugin};
250 if (!is_subclass_of($instance, 'phpbb_plugin_support'))
252 trigger_error('The Class ' . get_class($instance) . ' defined in ' . get_class($object) . ' is not pluggable.', E_USER_ERROR);
256 // Setup/Register plugin...
257 $object->setup_plugin($instance);
261 // Now setup hooks... this is a special case...
262 foreach ($this->functions as $key => $params)
264 if (isset($this->already_parsed_plugins['hook'][$key]))
266 continue;
269 $function = array_shift($params);
270 $hook = array_shift($params);
271 $mode = (!empty($params)) ? array_shift($params) : phpbb::FUNCTION_INJECT;
272 $action = (!empty($params)) ? array_shift($params) : 'default';
274 // Check if the function is already overridden.
275 if ($mode == phpbb::FUNCTION_OVERRIDE && isset($this->hooks[$function][$mode]))
277 trigger_error('Function ' . $function . ' is already overridden by ' . $this->hooks[$function][$mode] . '.', E_USER_ERROR);
280 if ($mode == phpbb::FUNCTION_OVERRIDE)
282 $this->hooks[$function][$mode] = $hook;
284 else
286 $this->hooks[$function][$mode][$action][] = $hook;
289 $this->already_parsed_plugins['hook'][$key] = true;
292 // Init method call and setting as finished
293 foreach ($this->plugins as $name => $plugin)
295 if (isset($this->already_parsed_plugins['plugin'][$name]))
297 continue;
300 // Call plugins init method?
301 if (method_exists($plugin->setup, 'init'))
303 $plugin->setup->init();
306 $this->already_parsed_plugins['plugin'][$name] = true;
311 * Register files to be included on plugin setup
312 * Used by plugin setup
314 * @param string $arguments,... List of files to include
315 * @access public
317 public function register_includes()
319 $arguments = func_get_args();
320 $this->current_plugin->includes = $arguments;
324 * Define the plugins classes registered within the setup process.
325 * Used by plugin setup.
327 * @param string $arguments,... List of classes to instantiate
328 * @access public
330 public function register_plugins()
332 $arguments = func_get_args();
334 // Make sure the class names are unique by checking for the phpbb name
335 foreach ($arguments as $class)
337 if (strpos($class, '_' . $this->current_plugin->phpbb_name . '_') === false)
339 trigger_error('Class ' . $class . ' has an invalid name in plugin ' . $this->current_plugin->phpbb_name . '. The class must include the name of the plugin.', E_USER_ERROR);
343 $this->current_plugin->objects = $arguments;
347 * Define the hook setup for functions.
348 * Used by plugin setup.
350 * @param string $function The function name to hook into
351 * @param string $hook The hooks function name
352 * @param phpbb::FUNCTION_OVERRIDE|phpbb::FUNCTION_INJECT $mode
353 * If set to phpbb::FUNCTION_OVERRIDE, the hook is called instead of the function and returns the result.
354 * If set to phpbb::FUNCTION_INJECT (default), then the hook is called based on the $action parameter
355 * @param string $action Defines the hooks action. Default parameters are 'default' and 'return'.
356 * Other actions are defined within the relevant functions and are documented within the plugins documentation.
357 * 'default': This is the default action used for the first hook called, usually at the start of the function. Parameter are passed by reference.
358 * 'return': This is the action used to specify the hook used at the end of the function. Result is passed, returns the result.
360 * @access public
362 public function register_function()
364 $arguments = func_get_args();
365 $this->current_plugin->functions[] = $this->functions[] = $arguments;
369 * Checks if a specific function is overridden by a hook
370 * Called within functions to check if they are overridden.
372 * @param string $function The functions name, usually passed as __FUNCTION__
374 * @return bool True if the function is overridden, false if not
375 * @access public
377 public function function_override($function)
379 return isset($this->hooks[$function][phpbb::FUNCTION_OVERRIDE]);
383 * Checks if a specific function is injected by a hook
384 * Called within functions to check if they are injected.
386 * @param string $function The functions name, usually passed as __FUNCTION__
387 * @param string $action The action to check. Can be 'default', 'return' or any other action defined.
389 * @return bool True if the function is injected for the particular action, false if not
390 * @access public
392 public function function_inject($function, $action = 'default')
394 return isset($this->hooks[$function][phpbb::FUNCTION_INJECT][$action]);
398 * Call hook function overriding function.
399 * This is called within a function to actually override the function with the hook.
401 * @param string $function The function name, usually passed as __FUNCTION__
402 * @param mixed $arguments,... Optional number of arguments passed by the function to the hook
404 * @return mixed The hooks result
405 * @access public
407 public function call_override()
409 $arguments = func_get_args();
410 $function = array_shift($arguments);
412 return call_user_func_array($this->hooks[$function][phpbb::FUNCTION_OVERRIDE], $arguments);
416 * Call injected function.
417 * This is called within functions to call hooks for specific actions.
419 * @param string $function The function name, usually passed as __FUNCTION__
420 * @param array $arguments Arguments passed to the hook as an array
421 * The first parameter is the action.
422 * If the action is 'return', the second parameter is the result
423 * The remaining parameter are the functions parameter, passed by reference if action is not 'return'.
425 * @return mixed Returns result if action is 'return'
426 * @access public
428 public function call_inject($function, $arguments)
430 $result = NULL;
432 if (!is_array($arguments))
434 $action = $arguments;
435 $arguments = array();
437 else
439 $action = array_shift($arguments);
442 // Return action... handle like override
443 if ($action == 'return')
445 $result = array_shift($arguments);
447 foreach ($this->hooks[$function][phpbb::FUNCTION_INJECT][$action] as $key => $hook)
449 $args = array_merge(array($result), $arguments);
450 $result = call_user_func_array($hook, $args);
453 return $result;
456 foreach ($this->hooks[$function][phpbb::FUNCTION_INJECT][$action] as $key => $hook)
458 call_user_func_array($hook, $arguments);
464 * Plugin support class.
465 * Objects supporting plugins must extend this class
466 * @package plugins
468 abstract class phpbb_plugin_support
471 * @var array Methods injected
473 private $plugin_methods;
476 * @var array Attributes injected
478 private $plugin_attributes;
481 * Register a new method, overrides one or inject into one.
483 * @param string $name The method name to inject into. False if a new method is registered.
484 * @param string $method The hook name to use.
485 * @param object $object Always $this
486 * @param phpbb::METHOD_ADD|phpbb::METHOD_OVERRIDE|phpbb::METHOD_INJECT $mode
487 * If set to phpbb::METHOD_ADD (default) the $name is added as a new method for the object plugged into.
488 * If set to phpbb::METHOD_OVERRIDE the hook is called instead of the method and returns the result.
489 * If set to phpbb::METHOD_INJECT, then the hook is called based on the $action parameter
490 * @param string $action Defines the hooks action. Default parameters are 'default' and 'return'.
491 * Other actions are defined within the relevant methods and are documented within the plugins documentation.
492 * 'default': This is the default action used for the first hook called, usually at the start of the method. Parameter are passed by reference.
493 * 'return': This is the action used to specify the hook used at the end of the method. Result is passed, returns the result.
495 * @access public
497 public function register_method($name, $method, $object, $mode = phpbb::METHOD_ADD, $action = 'default')
499 // Method reachable by:
500 // For plugin_add: plugin_methods[method] = object
501 // For plugin_override: plugin_methods[name][mode][method] = object
502 // For plugin_inject: plugin_methods[name][mode][action][method] = object
504 // Set to PLUGIN_ADD if method does not exist
505 if ($name === false || !method_exists($this, $name))
507 $mode = phpbb::METHOD_ADD;
510 // But if it exists and we try to add one, then print out an error
511 if ($mode == phpbb::METHOD_ADD && (method_exists($this, $method) || isset($this->plugin_methods[$method])))
513 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);
516 // Check if the same method name is already used for $name for overriding the method.
517 if ($mode == phpbb::METHOD_OVERRIDE && isset($this->plugin_methods[$name][$mode][$method]))
519 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);
522 // Check if another method is already defined...
523 if ($mode == phpbb::METHOD_INJECT && isset($this->plugin_methods[$name][$mode][$action][$method]))
525 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);
528 if (($function_signature = $this->valid_parameter($object, $method, $mode, $action)) !== true)
530 trigger_error('Method ' . $method . ' in class ' . get_class($object) . ' has invalid function signature. Please use: ' . $function_signature, E_USER_ERROR);
533 if ($mode == phpbb::METHOD_ADD)
535 $this->plugin_methods[$method] = $object;
537 else if ($mode == phpbb::METHOD_OVERRIDE)
539 $this->plugin_methods[$name][$mode][$method] = $object;
541 else
543 $this->plugin_methods[$name][$mode][$action][$method] = $object;
548 * Register a new attribute.
549 * If the attribute already exists within the object then it will be overwritten.
551 * @param string $name Attribute name to register.
552 * @param object $object Always $this
554 * @access public
556 public function register_attribute($name, $object)
558 if (property_exists($this, $name))
560 unset($this->$name);
563 if (isset($this->plugin_attributes[$name]))
565 trigger_error('Attribute ' . $name . ' in class ' . get_class($object) . ' already defined in class ' . get_class($this->plugin_attributes[$name]), E_USER_ERROR);
568 $this->plugin_attributes[$name] = $object;
571 /**#@+
572 * Magic method for attributes. See {@link register_attribute() register_attribute}.
573 * @access public
575 public function __get($name)
577 if (isset($this->plugin_attributes[$name]))
579 return $this->plugin_attributes[$name]->$name;
582 return $this->$name;
585 public function __set($name, $value)
587 if (isset($this->plugin_attributes[$name]))
589 return $this->plugin_attributes[$name]->$name = $value;
592 return $this->$name = $value;
595 public function __isset($name)
597 if (isset($this->plugin_attributes[$name]))
599 return isset($this->plugin_attributes[$name]->$name);
602 return isset($this->$name);
605 public function __unset($name)
607 if (isset($this->plugin_attributes[$name]))
609 unset($this->plugin_attributes[$name]->$name);
612 unset($this->$name);
614 /**#@-*/
617 * Call added method. See {@link register_method() register_method} with phpbb::METHOD_ADD mode.
618 * @access public
620 public function __call($name, $arguments)
622 if (isset($this->plugin_methods[$name]) && !is_array($this->plugin_methods[$name]))
624 array_unshift($arguments, $this);
625 return call_user_func_array(array($this->plugin_methods[$name], $name), $arguments);
628 trigger_error('Call to undefined method ' . $name . '() in ' . get_class($this) . '.', E_USER_ERROR);
632 * Checks if a specific method is overridden by a hook
633 * Called within methods to check if they are overridden.
635 * @param string $name The methods name, usually passed as __FUNCTION__
637 * @return bool True if the method is overridden, false if not
638 * @access protected
640 protected function method_override($name)
642 return isset($this->plugin_methods[$name][phpbb::METHOD_OVERRIDE]);
646 * Checks if a specific method is injected by a hook
647 * Called within methods to check if they are injected.
649 * @param string $name The methods name, usually passed as __FUNCTION__
650 * @param string $action The action to check. Can be 'default', 'return' or any other action defined.
652 * @return bool True if the method is injected for the particular action, false if not
653 * @access protected
655 protected function method_inject($name, $action = 'default')
657 return isset($this->plugin_methods[$name][phpbb::METHOD_INJECT][$action]);
661 * Call hook method overriding method.
662 * This is called within a method to actually override the method with the hook.
664 * @param string $name The method name, usually passed as __FUNCTION__
665 * @param mixed $arguments,... Optional number of arguments passed by the method to the hook
667 * @return mixed The hooks result
668 * @access protected
670 protected function call_override()
672 $arguments = func_get_args();
673 $name = array_shift($arguments);
675 list($method, $object) = each($this->plugin_methods[$name][phpbb::METHOD_OVERRIDE]);
676 return call_user_func_array(array($object, $method), array_merge(array($this), $arguments));
680 * Call injected method.
681 * This is called within methods to call hooks for specific actions.
683 * @param string $name The method name, usually passed as __FUNCTION__
684 * @param array $arguments Arguments passed to the hook as an array
685 * The first parameter is the action.
686 * If the action is 'return', the second parameter is the result
687 * The remaining parameter are the methods parameter, passed by reference if action is not 'return'.
689 * @return mixed Returns result if action is 'return'
690 * @access protected
692 protected function call_inject($name, $arguments)
694 $result = NULL;
696 if (!is_array($arguments))
698 $action = $arguments;
699 $arguments = array();
701 else
703 $action = array_shift($arguments);
706 // Return action... handle like override
707 if ($action == 'return')
709 $result = array_shift($arguments);
711 foreach ($this->plugin_methods[$name][phpbb::METHOD_INJECT][$action] as $method => $object)
713 $args = array_merge(array($this, $result), $arguments);
714 $result = call_user_func_array(array($object, $method), $args);
717 return $result;
720 foreach ($this->plugin_methods[$name][phpbb::METHOD_INJECT][$action] as $method => $object)
722 call_user_func_array(array($object, $method), array_merge(array($this), $arguments));
727 * Check function signature for passed methods in {@link register_method() register_method()}.
729 * @param object $object The plugin
730 * @param string $method The method name
731 * @param phpbb::METHOD_ADD|phpbb::METHOD_OVERRIDE|phpbb::METHOD_INJECT $mode The mode
732 * @param string $action The action
734 * @return mixed True if the signature is valid, else the correct function signature
735 * @access private
737 private function valid_parameter($object, $method, $mode, $action)
739 // We cache the results... no worry. These checks are quite resource intensive, but will hopefully educate and guide developers
741 // Check for correct first parameter. This must be an instance of phpbb_$phpbb_plugin
742 $instance_of = 'phpbb_' . $object->phpbb_plugin;
744 // Define the required function layout
745 $function_layout = 'public function ' . $method . '(' . $instance_of . ' $object';
747 // Result for METHOD_INJECT and action == 'return'
748 if ($mode == phpbb::METHOD_INJECT && $action == 'return')
750 $function_layout .= ', $result';
753 // Optional method parameter
754 $function_layout .= ', [...]) { [...] }';
756 // Now check the method
757 $reflection = new ReflectionMethod($object, $method);
758 $parameters = $reflection->getParameters();
760 // First parameter needs to be defined
761 $first_param = array_shift($parameters);
763 // Signature is wrong if first parameter is empty
764 if (empty($first_param))
766 return $function_layout;
769 // Try to get class from first parameter
772 $first_param->getClass()->name;
774 catch (Exception $e)
776 return $function_layout;
779 // First parameter needs to be an instance of phpbb_$phpbb_plugin and parameter must be $object
780 if ($first_param->getClass()->name !== $instance_of || $first_param->getName() !== 'object')
782 return $function_layout;
785 // If the action is 'return' we also check for an existing $result parameter
786 if ($mode == phpbb::METHOD_INJECT && $action == 'return')
788 $first_param = array_shift($parameters);
790 // If no result is passed, or the name not $result or the $result being optional, then the signature is wrong
791 if (empty($first_param) || $first_param->getName() !== 'result' || $first_param->isOptional())
793 return $function_layout;
797 // Everything ok
798 return true;
803 * Interface for phpBB plugin info
805 interface phpbb_plugin_info
808 * Setup plugin information
810 * @param phpbb_plugins $object The {@link phpbb_plugins plugins object} passed
812 public function setup_plugin(phpbb_plugins $object);
816 * Interface for phpBB plugin setup
818 interface phpbb_plugin_setup
821 * Setup specific class within the plugin
823 * @param phpbb_plugin_support $object The injected object... supports {@link phpbb_plugin_support plugins}
825 function setup_plugin(phpbb_plugin_support $object);