| | |
| | | | program/include/rcube_plugin_api.php | |
| | | | | |
| | | | This file is part of the Roundcube Webmail client | |
| | | | Copyright (C) 2008-2009, The Roundcube Dev Team | |
| | | | Licensed under the GNU GPL | |
| | | | Copyright (C) 2008-2011, The Roundcube Dev Team | |
| | | | | |
| | | | Licensed under the GNU General Public License version 3 or | |
| | | | any later version with exceptions for skins & plugins. | |
| | | | See the README file for a full license statement. | |
| | | | | |
| | | | PURPOSE: | |
| | | | Plugins repository | |
| | |
| | | +-----------------------------------------------------------------------+ |
| | | | Author: Thomas Bruederli <roundcube@gmail.com> | |
| | | +-----------------------------------------------------------------------+ |
| | | |
| | | $Id$ |
| | | |
| | | */ |
| | | |
| | | // location where plugins are loade from |
| | | if (!defined('RCMAIL_PLUGINS_DIR')) |
| | | define('RCMAIL_PLUGINS_DIR', INSTALL_PATH . 'plugins/'); |
| | | |
| | | |
| | | /** |
| | | * The plugin loader and global API |
| | | * |
| | | * @package PluginAPI |
| | | * @package Framework |
| | | * @subpackage PluginAPI |
| | | */ |
| | | class rcube_plugin_api |
| | | { |
| | | static private $instance; |
| | | |
| | | static protected $instance; |
| | | |
| | | public $dir; |
| | | public $url = 'plugins/'; |
| | | public $task = ''; |
| | | public $output; |
| | | public $config; |
| | | |
| | | |
| | | public $handlers = array(); |
| | | private $plugins = array(); |
| | | private $tasks = array(); |
| | | private $actions = array(); |
| | | private $actionmap = array(); |
| | | private $objectsmap = array(); |
| | | private $template_contents = array(); |
| | | private $required_plugins = array('filesystem_attachments'); |
| | | private $active_hook = false; |
| | | protected $plugins = array(); |
| | | protected $tasks = array(); |
| | | protected $actions = array(); |
| | | protected $actionmap = array(); |
| | | protected $objectsmap = array(); |
| | | protected $template_contents = array(); |
| | | protected $active_hook = false; |
| | | |
| | | // Deprecated names of hooks, will be removed after 0.5-stable release |
| | | private $deprecated_hooks = array( |
| | | protected $deprecated_hooks = array( |
| | | 'create_user' => 'user_create', |
| | | 'kill_session' => 'session_destroy', |
| | | 'upload_attachment' => 'attachment_upload', |
| | |
| | | 'delete_identity' => 'identity_delete', |
| | | 'save_identity' => 'identity_update', |
| | | 'identity_save' => 'identity_update', |
| | | // to be removed after 0.8 |
| | | 'imap_init' => 'storage_init', |
| | | 'mailboxes_list' => 'storage_folders', |
| | | ); |
| | | |
| | | /** |
| | |
| | | /** |
| | | * Private constructor |
| | | */ |
| | | private function __construct() |
| | | protected function __construct() |
| | | { |
| | | $this->dir = INSTALL_PATH . $this->url; |
| | | $this->dir = slashify(RCMAIL_PLUGINS_DIR); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Initialize plugin engine |
| | | * |
| | | * This has to be done after rcmail::load_gui() or rcmail::json_init() |
| | | * was called because plugins need to have access to rcmail->output |
| | | * |
| | | * @param object rcube Instance of the rcube base class |
| | | * @param string Current application task (used for conditional plugin loading) |
| | | */ |
| | | public function init($app, $task = '') |
| | | { |
| | | $this->task = $task; |
| | | $this->output = $app->output; |
| | | |
| | | // register an internal hook |
| | | $this->register_hook('template_container', array($this, 'template_container_hook')); |
| | | |
| | | // maybe also register a shudown function which triggers shutdown functions of all plugin objects |
| | | } |
| | | |
| | | |
| | |
| | | * |
| | | * This has to be done after rcmail::load_gui() or rcmail::json_init() |
| | | * was called because plugins need to have access to rcmail->output |
| | | * |
| | | * @param array List of configured plugins to load |
| | | * @param array List of plugins required by the application |
| | | */ |
| | | public function init() |
| | | public function load_plugins($plugins_enabled, $required_plugins = array()) |
| | | { |
| | | $rcmail = rcmail::get_instance(); |
| | | $this->output = $rcmail->output; |
| | | $this->config = $rcmail->config; |
| | | |
| | | $plugins_enabled = (array)$rcmail->config->get('plugins', array()); |
| | | foreach ($plugins_enabled as $plugin_name) { |
| | | $this->load_plugin($plugin_name); |
| | | } |
| | | |
| | | // check existance of all required core plugins |
| | | foreach ($this->required_plugins as $plugin_name) { |
| | | foreach ($required_plugins as $plugin_name) { |
| | | $loaded = false; |
| | | foreach ($this->plugins as $plugin) { |
| | | if ($plugin instanceof $plugin_name) { |
| | |
| | | |
| | | // trigger fatal error if still not loaded |
| | | if (!$loaded) { |
| | | raise_error(array('code' => 520, 'type' => 'php', |
| | | rcube::raise_error(array('code' => 520, 'type' => 'php', |
| | | 'file' => __FILE__, 'line' => __LINE__, |
| | | 'message' => "Requried plugin $plugin_name was not loaded"), true, true); |
| | | } |
| | | } |
| | | |
| | | // register an internal hook |
| | | $this->register_hook('template_container', array($this, 'template_container_hook')); |
| | | |
| | | // maybe also register a shudown function which triggers shutdown functions of all plugin objects |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Load the specified plugin |
| | |
| | | public function load_plugin($plugin_name) |
| | | { |
| | | static $plugins_dir; |
| | | |
| | | $rcmail = rcmail::get_instance(); |
| | | |
| | | if (!$plugins_dir) { |
| | | $dir = dir($this->dir); |
| | |
| | | // check inheritance... |
| | | if (is_subclass_of($plugin, 'rcube_plugin')) { |
| | | // ... task, request type and framed mode |
| | | if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $rcmail->task)) |
| | | && (!$plugin->noajax || (is_object($rcmail->output) && is_a($rcmail->output, 'rcube_template'))) |
| | | if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $this->task)) |
| | | && (!$plugin->noajax || (is_object($this->output) && $this->output->type == 'html')) |
| | | && (!$plugin->noframe || empty($_REQUEST['_framed'])) |
| | | ) { |
| | | $plugin->init(); |
| | |
| | | } |
| | | } |
| | | else { |
| | | raise_error(array('code' => 520, 'type' => 'php', |
| | | rcube::raise_error(array('code' => 520, 'type' => 'php', |
| | | 'file' => __FILE__, 'line' => __LINE__, |
| | | 'message' => "No plugin class $plugin_name found in $fn"), true, false); |
| | | } |
| | | } |
| | | else { |
| | | raise_error(array('code' => 520, 'type' => 'php', |
| | | rcube::raise_error(array('code' => 520, 'type' => 'php', |
| | | 'file' => __FILE__, 'line' => __LINE__, |
| | | 'message' => "Failed to load plugin file $fn"), true, false); |
| | | } |
| | |
| | | { |
| | | if (is_callable($callback)) { |
| | | if (isset($this->deprecated_hooks[$hook])) { |
| | | raise_error(array('code' => 522, 'type' => 'php', |
| | | rcube::raise_error(array('code' => 522, 'type' => 'php', |
| | | 'file' => __FILE__, 'line' => __LINE__, |
| | | 'message' => "Deprecated hook name. ".$hook.' -> '.$this->deprecated_hooks[$hook]), true, false); |
| | | $hook = $this->deprecated_hooks[$hook]; |
| | |
| | | $this->handlers[$hook][] = $callback; |
| | | } |
| | | else |
| | | raise_error(array('code' => 521, 'type' => 'php', |
| | | rcube::raise_error(array('code' => 521, 'type' => 'php', |
| | | 'file' => __FILE__, 'line' => __LINE__, |
| | | 'message' => "Invalid callback function for $hook"), true, false); |
| | | } |
| | | |
| | | /** |
| | | * Allow a plugin object to unregister a callback. |
| | | * |
| | | * @param string $hook Hook name |
| | | * @param mixed $callback String with global function name or array($obj, 'methodname') |
| | | */ |
| | | public function unregister_hook($hook, $callback) |
| | | { |
| | | $callback_id = array_search($callback, $this->handlers[$hook]); |
| | | if ($callback_id !== false) { |
| | | unset($this->handlers[$hook][$callback_id]); |
| | | } |
| | | } |
| | | |
| | | |
| | |
| | | $this->actionmap[$action] = $owner; |
| | | } |
| | | else { |
| | | raise_error(array('code' => 523, 'type' => 'php', |
| | | rcube::raise_error(array('code' => 523, 'type' => 'php', |
| | | 'file' => __FILE__, 'line' => __LINE__, |
| | | 'message' => "Cannot register action $action; already taken by another plugin"), true, false); |
| | | } |
| | |
| | | if (isset($this->actions[$action])) { |
| | | call_user_func($this->actions[$action]); |
| | | } |
| | | else { |
| | | raise_error(array('code' => 524, 'type' => 'php', |
| | | else if (rcube::get_instance()->action != 'refresh') { |
| | | rcube::raise_error(array('code' => 524, 'type' => 'php', |
| | | 'file' => __FILE__, 'line' => __LINE__, |
| | | 'message' => "No handler found for action $action"), true, true); |
| | | } |
| | |
| | | $name = 'plugin.'.$name; |
| | | |
| | | // can register handler only if it's not taken or registered by myself |
| | | if (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner) { |
| | | if (is_object($this->output) && (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner)) { |
| | | $this->output->add_handler($name, $callback); |
| | | $this->objectsmap[$name] = $owner; |
| | | } |
| | | else { |
| | | raise_error(array('code' => 525, 'type' => 'php', |
| | | rcube::raise_error(array('code' => 525, 'type' => 'php', |
| | | 'file' => __FILE__, 'line' => __LINE__, |
| | | 'message' => "Cannot register template handler $name; already taken by another plugin"), true, false); |
| | | 'message' => "Cannot register template handler $name; already taken by another plugin or no output object available"), true, false); |
| | | } |
| | | } |
| | | |
| | |
| | | public function register_task($task, $owner) |
| | | { |
| | | if ($task != asciiwords($task)) { |
| | | raise_error(array('code' => 526, 'type' => 'php', |
| | | rcube::raise_error(array('code' => 526, 'type' => 'php', |
| | | 'file' => __FILE__, 'line' => __LINE__, |
| | | 'message' => "Invalid task name: $task. Only characters [a-z0-9_.-] are allowed"), true, false); |
| | | } |
| | | else if (in_array($task, rcmail::$main_tasks)) { |
| | | raise_error(array('code' => 526, 'type' => 'php', |
| | | rcube::raise_error(array('code' => 526, 'type' => 'php', |
| | | 'file' => __FILE__, 'line' => __LINE__, |
| | | 'message' => "Cannot register taks $task; already taken by another plugin or the application itself"), true, false); |
| | | } |
| | |
| | | */ |
| | | public function include_script($fn) |
| | | { |
| | | if ($this->output->type == 'html') { |
| | | if (is_object($this->output) && $this->output->type == 'html') { |
| | | $src = $this->resource_url($fn); |
| | | $this->output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $src))); |
| | | } |
| | |
| | | */ |
| | | public function include_stylesheet($fn) |
| | | { |
| | | if ($this->output->type == 'html') { |
| | | if (is_object($this->output) && $this->output->type == 'html') { |
| | | $src = $this->resource_url($fn); |
| | | $this->output->include_css($src); |
| | | } |
| | |
| | | |
| | | |
| | | /** |
| | | * Returns list of loaded plugins names |
| | | * |
| | | * @return array List of plugin names |
| | | */ |
| | | public function loaded_plugins() |
| | | { |
| | | return array_keys($this->plugins); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Callback for template_container hooks |
| | | * |
| | | * @param array $attrib |
| | | * @return array |
| | | */ |
| | | private function template_container_hook($attrib) |
| | | protected function template_container_hook($attrib) |
| | | { |
| | | $container = $attrib['name']; |
| | | return array('content' => $attrib['content'] . $this->template_contents[$container]); |
| | |
| | | * @param string $fn Filename |
| | | * @return string |
| | | */ |
| | | private function resource_url($fn) |
| | | protected function resource_url($fn) |
| | | { |
| | | if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn)) |
| | | return $this->url . $fn; |