4 +-----------------------------------------------------------------------+
5 | program/include/rcube_plugin_api.php |
7 | This file is part of the RoundCube Webmail client |
8 | Copyright (C) 2008-2009, RoundCube Dev. - Switzerland |
9 | Licensed under the GNU GPL |
12 | Plugins repository |
14 +-----------------------------------------------------------------------+
15 | Author: Thomas Bruederli <roundcube@gmail.com> |
16 +-----------------------------------------------------------------------+
23 * The plugin loader and global API
27 class rcube_plugin_api
29 static private $instance;
32 public $url = 'plugins/';
35 public $handlers = array();
36 private $plugins = array();
37 private $actions = array();
38 private $actionmap = array();
39 private $objectsmap = array();
40 private $template_contents = array();
42 private $required_plugins = array('filesystem_attachments');
43 private $active_hook = false;
46 * This implements the 'singleton' design pattern
48 * @return object rcube_plugin_api The one and only instance if this class
50 static function get_instance()
52 if (!self::$instance) {
53 self::$instance = new rcube_plugin_api();
56 return self::$instance;
63 private function __construct()
65 $this->dir = INSTALL_PATH . $this->url;
70 * Load and init all enabled plugins
72 * This has to be done after rcmail::load_gui() or rcmail::init_json()
73 * was called because plugins need to have access to rcmail->output
75 public function init()
77 $rcmail = rcmail::get_instance();
78 $this->output = $rcmail->output;
80 $plugins_dir = dir($this->dir);
81 $plugins_enabled = (array)$rcmail->config->get('plugins', array());
83 foreach ($plugins_enabled as $plugin_name) {
84 $fn = $plugins_dir->path . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php';
86 if (file_exists($fn)) {
89 // instantiate class if exists
90 if (class_exists($plugin_name, false)) {
91 $plugin = new $plugin_name($this);
92 // check inheritance and task specification
93 if (is_subclass_of($plugin, 'rcube_plugin') && (!$plugin->task || preg_match('/('.$plugin->task.')/i', $rcmail->task))) {
95 $this->plugins[] = $plugin;
99 raise_error(array('code' => 520, 'type' => 'php', 'message' => "No plugin class $plugin_name found in $fn"), true, false);
103 raise_error(array('code' => 520, 'type' => 'php', 'message' => "Failed to load plugin file $fn"), true, false);
107 // check existance of all required core plugins
108 foreach ($this->required_plugins as $plugin_name) {
110 foreach ($this->plugins as $plugin) {
111 if ($plugin instanceof $plugin_name) {
117 // load required core plugin if no derivate was found
119 $fn = $plugins_dir->path . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php';
120 if (file_exists($fn)) {
123 if (class_exists($plugin_name, false)) {
124 $plugin = new $plugin_name($this);
126 if (is_subclass_of($plugin, 'rcube_plugin')) {
127 if (!$plugin->task || preg_match('/('.$plugin->task.')/i', $rcmail->task)) {
129 $this->plugins[] = $plugin;
137 // trigger fatal error if still not loaded
139 raise_error(array('code' => 520, 'type' => 'php', 'message' => "Requried plugin $plugin_name was not loaded"), true, true);
143 // register an internal hook
144 $this->register_hook('template_container', array($this, 'template_container_hook'));
146 // maybe also register a shudown function which triggers shutdown functions of all plugin objects
149 // call imap_init right now
150 // (should actually be done in rcmail::imap_init() but plugins are not initialized then)
152 $hook = $this->exec_hook('imap_init', array('fetch_headers' => $rcmail->imap->fetch_add_headers));
153 if ($hook['fetch_headers'])
154 $rcmail->imap->fetch_add_headers = $hook['fetch_headers'];
160 * Allows a plugin object to register a callback for a certain hook
162 * @param string Hook name
163 * @param mixed String with global function name or array($obj, 'methodname')
165 public function register_hook($hook, $callback)
167 if (is_callable($callback))
168 $this->handlers[$hook][] = $callback;
170 raise_error(array('code' => 521, 'type' => 'php', 'message' => "Invalid callback function for $hook"), true, false);
175 * Triggers a plugin hook.
176 * This is called from the application and executes all registered handlers
178 * @param string Hook name
179 * @param array Named arguments (key->value pairs)
180 * @return array The (probably) altered hook arguments
182 public function exec_hook($hook, $args = array())
184 $args += array('abort' => false);
185 $this->active_hook = $hook;
187 foreach ((array)$this->handlers[$hook] as $callback) {
188 $ret = call_user_func($callback, $args);
189 if ($ret && is_array($ret))
190 $args = $ret + $args;
196 $this->active_hook = false;
202 * Let a plugin register a handler for a specific request
204 * @param string Action name (_task=mail&_action=plugin.foo)
205 * @param string Plugin name that registers this action
206 * @param mixed Callback: string with global function name or array($obj, 'methodname')
208 public function register_action($action, $owner, $callback)
211 if (strpos($action, 'plugin.') !== 0)
212 $action = 'plugin.'.$action;
214 // can register action only if it's not taken or registered by myself
215 if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) {
216 $this->actions[$action] = $callback;
217 $this->actionmap[$action] = $owner;
220 raise_error(array('code' => 523, 'type' => 'php', 'message' => "Cannot register action $action; already taken by another plugin"), true, false);
226 * This method handles requests like _task=mail&_action=plugin.foo
227 * It executes the callback function that was registered with the given action.
229 * @param string Action name
231 public function exec_action($action)
233 if (isset($this->actions[$action])) {
234 call_user_func($this->actions[$action]);
237 raise_error(array('code' => 524, 'type' => 'php', 'message' => "No handler found for action $action"), true, true);
243 * Register a handler function for template objects
245 * @param string Object name
246 * @param string Plugin name that registers this action
247 * @param mixed Callback: string with global function name or array($obj, 'methodname')
249 public function register_handler($name, $owner, $callback)
252 if (strpos($name, 'plugin.') !== 0)
253 $name = 'plugin.'.$name;
255 // can register handler only if it's not taken or registered by myself
256 if (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner) {
257 $this->output->add_handler($name, $callback);
258 $this->objectsmap[$name] = $owner;
261 raise_error(array('code' => 525, 'type' => 'php', 'message' => "Cannot register template handler $name; already taken by another plugin"), true, false);
267 * Check if a plugin hook is currently processing.
268 * Mainly used to prevent loops and recursion.
270 * @param string Hook to check (optional)
271 * @return boolean True if any/the given hook is currently processed, otherwise false
273 public function is_processing($hook = null)
275 return $this->active_hook && (!$hook || $this->active_hook == $hook);
279 * Include a plugin script file in the current HTML page
281 public function include_script($fn)
283 if ($this->output->type == 'html') {
284 $src = $this->resource_url($fn);
285 $this->output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $src)));
290 * Include a plugin stylesheet in the current HTML page
292 public function include_stylesheet($fn)
294 if ($this->output->type == 'html') {
295 $src = $this->resource_url($fn);
296 $this->output->add_header(html::tag('link', array('rel' => "stylesheet", 'type' => "text/css", 'href' => $src)));
301 * Save the given HTML content to be added to a template container
303 public function add_content($html, $container)
305 $this->template_contents[$container] .= $html . "\n";
309 * Callback for template_container hooks
311 private function template_container_hook($attrib)
313 $container = $attrib['name'];
314 return array('content' => $attrib['content'] . $this->template_contents[$container]);
318 * Make the given file name link into the plugins directory
320 private function resource_url($fn)
322 if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn))
323 return $this->url . $fn;