source: github/program/include/rcube_plugin_api.php @ 48bc52e

HEADcourier-fixdev-browser-capabilitiespdorelease-0.6release-0.7release-0.8
Last change on this file since 48bc52e was 48bc52e, checked in by alecpl <alec@…>, 3 years ago
  • Property mode set to 100644
File size: 10.0 KB
Line 
1<?php
2
3/*
4 +-----------------------------------------------------------------------+
5 | program/include/rcube_plugin_api.php                                  |
6 |                                                                       |
7 | This file is part of the RoundCube Webmail client                     |
8 | Copyright (C) 2008-2009, RoundCube Dev. - Switzerland                 |
9 | Licensed under the GNU GPL                                            |
10 |                                                                       |
11 | PURPOSE:                                                              |
12 |   Plugins repository                                                  |
13 |                                                                       |
14 +-----------------------------------------------------------------------+
15 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
16 +-----------------------------------------------------------------------+
17
18 $Id$
19
20*/
21
22/**
23 * The plugin loader and global API
24 *
25 * @package Core
26 */
27class rcube_plugin_api
28{
29  static private $instance;
30 
31  public $dir;
32  public $url = 'plugins/';
33  public $output;
34 
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();
41 
42  private $required_plugins = array('filesystem_attachments');
43  private $active_hook = false;
44
45  /**
46   * This implements the 'singleton' design pattern
47   *
48   * @return object rcube_plugin_api The one and only instance if this class
49   */
50  static function get_instance()
51  {
52    if (!self::$instance) {
53      self::$instance = new rcube_plugin_api();
54    }
55
56    return self::$instance;
57  }
58 
59 
60  /**
61   * Private constructor
62   */
63  private function __construct()
64  {
65    $this->dir = INSTALL_PATH . $this->url;
66  }
67 
68 
69  /**
70   * Load and init all enabled plugins
71   *
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
74   */
75  public function init()
76  {
77    $rcmail = rcmail::get_instance();
78    $this->output = $rcmail->output;
79   
80    $plugins_dir = dir($this->dir);
81    $plugins_enabled = (array)$rcmail->config->get('plugins', array());
82   
83    foreach ($plugins_enabled as $plugin_name) {
84      $fn = $plugins_dir->path . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php';
85     
86      if (file_exists($fn)) {
87        include($fn);
88       
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))) {
94            $plugin->init();
95            $this->plugins[] = $plugin;
96          }
97        }
98        else {
99          raise_error(array('code' => 520, 'type' => 'php',
100            'file' => __FILE__, 'line' => __LINE__,
101            'message' => "No plugin class $plugin_name found in $fn"), true, false);
102        }
103      }
104      else {
105        raise_error(array('code' => 520, 'type' => 'php',
106          'file' => __FILE__, 'line' => __LINE__,
107          'message' => "Failed to load plugin file $fn"), true, false);
108      }
109    }
110   
111    // check existance of all required core plugins
112    foreach ($this->required_plugins as $plugin_name) {
113      $loaded = false;
114      foreach ($this->plugins as $plugin) {
115        if ($plugin instanceof $plugin_name) {
116          $loaded = true;
117          break;
118        }
119      }
120     
121      // load required core plugin if no derivate was found
122      if (!$loaded) {
123        $fn = $plugins_dir->path . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php';
124        if (file_exists($fn)) {
125          include_once($fn);
126         
127          if (class_exists($plugin_name, false)) {
128            $plugin = new $plugin_name($this);
129            // check inheritance
130            if (is_subclass_of($plugin, 'rcube_plugin')) {
131              if (!$plugin->task || preg_match('/('.$plugin->task.')/i', $rcmail->task)) {
132                $plugin->init();
133                $this->plugins[] = $plugin;
134              }
135              $loaded = true;
136            }
137          }
138        }
139      }
140     
141      // trigger fatal error if still not loaded
142      if (!$loaded) {
143        raise_error(array('code' => 520, 'type' => 'php',
144          'file' => __FILE__, 'line' => __LINE__,
145          'message' => "Requried plugin $plugin_name was not loaded"), true, true);
146      }
147    }
148
149    // register an internal hook
150    $this->register_hook('template_container', array($this, 'template_container_hook'));
151   
152    // maybe also register a shudown function which triggers shutdown functions of all plugin objects
153  }
154 
155 
156  /**
157   * Allows a plugin object to register a callback for a certain hook
158   *
159   * @param string Hook name
160   * @param mixed String with global function name or array($obj, 'methodname')
161   */
162  public function register_hook($hook, $callback)
163  {
164    if (is_callable($callback))
165      $this->handlers[$hook][] = $callback;
166    else
167      raise_error(array('code' => 521, 'type' => 'php',
168        'file' => __FILE__, 'line' => __LINE__,
169        'message' => "Invalid callback function for $hook"), true, false);
170  }
171 
172 
173  /**
174   * Triggers a plugin hook.
175   * This is called from the application and executes all registered handlers
176   *
177   * @param string Hook name
178   * @param array Named arguments (key->value pairs)
179   * @return array The (probably) altered hook arguments
180   */
181  public function exec_hook($hook, $args = array())
182  {
183    if (!is_array($args))
184      $args = array('arg' => $args);
185
186    $args += array('abort' => false);
187    $this->active_hook = $hook;
188   
189    foreach ((array)$this->handlers[$hook] as $callback) {
190      $ret = call_user_func($callback, $args);
191      if ($ret && is_array($ret))
192        $args = $ret + $args;
193     
194      if ($args['abort'])
195        break;
196    }
197   
198    $this->active_hook = false;
199    return $args;
200  }
201
202
203  /**
204   * Let a plugin register a handler for a specific request
205   *
206   * @param string Action name (_task=mail&_action=plugin.foo)
207   * @param string Plugin name that registers this action
208   * @param mixed Callback: string with global function name or array($obj, 'methodname')
209   */
210  public function register_action($action, $owner, $callback)
211  {
212    // check action name
213    if (strpos($action, 'plugin.') !== 0)
214      $action = 'plugin.'.$action;
215   
216    // can register action only if it's not taken or registered by myself
217    if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) {
218      $this->actions[$action] = $callback;
219      $this->actionmap[$action] = $owner;
220    }
221    else {
222      raise_error(array('code' => 523, 'type' => 'php',
223        'file' => __FILE__, 'line' => __LINE__,
224        'message' => "Cannot register action $action; already taken by another plugin"), true, false);
225    }
226  }
227
228
229  /**
230   * This method handles requests like _task=mail&_action=plugin.foo
231   * It executes the callback function that was registered with the given action.
232   *
233   * @param string Action name
234   */
235  public function exec_action($action)
236  {
237    if (isset($this->actions[$action])) {
238      call_user_func($this->actions[$action]);
239    }
240    else {
241      raise_error(array('code' => 524, 'type' => 'php',
242        'file' => __FILE__, 'line' => __LINE__,
243        'message' => "No handler found for action $action"), true, true);
244    }
245  }
246
247
248  /**
249   * Register a handler function for template objects
250   *
251   * @param string Object name
252   * @param string Plugin name that registers this action
253   * @param mixed Callback: string with global function name or array($obj, 'methodname')
254   */
255  public function register_handler($name, $owner, $callback)
256  {
257    // check name
258    if (strpos($name, 'plugin.') !== 0)
259      $name = 'plugin.'.$name;
260   
261    // can register handler only if it's not taken or registered by myself
262    if (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner) {
263      $this->output->add_handler($name, $callback);
264      $this->objectsmap[$name] = $owner;
265    }
266    else {
267      raise_error(array('code' => 525, 'type' => 'php',
268        'file' => __FILE__, 'line' => __LINE__,
269        'message' => "Cannot register template handler $name; already taken by another plugin"), true, false);
270    }
271  }
272 
273 
274  /**
275   * Check if a plugin hook is currently processing.
276   * Mainly used to prevent loops and recursion.
277   *
278   * @param string Hook to check (optional)
279   * @return boolean True if any/the given hook is currently processed, otherwise false
280   */
281  public function is_processing($hook = null)
282  {
283    return $this->active_hook && (!$hook || $this->active_hook == $hook);
284  }
285 
286  /**
287   * Include a plugin script file in the current HTML page
288   */
289  public function include_script($fn)
290  {
291    if ($this->output->type == 'html') {
292      $src = $this->resource_url($fn);
293      $this->output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $src)));
294    }
295  }
296
297  /**
298   * Include a plugin stylesheet in the current HTML page
299   */
300  public function include_stylesheet($fn)
301  {
302    if ($this->output->type == 'html') {
303      $src = $this->resource_url($fn);
304      $this->output->add_header(html::tag('link', array('rel' => "stylesheet", 'type' => "text/css", 'href' => $src)));
305    }
306  }
307 
308  /**
309   * Save the given HTML content to be added to a template container
310   */
311  public function add_content($html, $container)
312  {
313    $this->template_contents[$container] .= $html . "\n";
314  }
315 
316  /**
317   * Callback for template_container hooks
318   */
319  private function template_container_hook($attrib)
320  {
321    $container = $attrib['name'];
322    return array('content' => $attrib['content'] . $this->template_contents[$container]);
323  }
324 
325  /**
326   * Make the given file name link into the plugins directory
327   */
328  private function resource_url($fn)
329  {
330    if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn))
331      return $this->url . $fn;
332    else
333      return $fn;
334  }
335
336}
337
Note: See TracBrowser for help on using the repository browser.