source: github/program/include/rcube_plugin_api.php @ 1d786c8

HEADcourier-fixdev-browser-capabilitiespdorelease-0.6release-0.7release-0.8
Last change on this file since 1d786c8 was 1d786c8, checked in by alecpl <alec@…>, 3 years ago
  • fixed svn keywords
  • 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', 'message' => "No plugin class $plugin_name found in $fn"), true, false);
100        }
101      }
102      else {
103        raise_error(array('code' => 520, 'type' => 'php', 'message' => "Failed to load plugin file $fn"), true, false);
104      }
105    }
106   
107    // check existance of all required core plugins
108    foreach ($this->required_plugins as $plugin_name) {
109      $loaded = false;
110      foreach ($this->plugins as $plugin) {
111        if ($plugin instanceof $plugin_name) {
112          $loaded = true;
113          break;
114        }
115      }
116     
117      // load required core plugin if no derivate was found
118      if (!$loaded) {
119        $fn = $plugins_dir->path . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php';
120        if (file_exists($fn)) {
121          include_once($fn);
122         
123          if (class_exists($plugin_name, false)) {
124            $plugin = new $plugin_name($this);
125            // check inheritance
126            if (is_subclass_of($plugin, 'rcube_plugin')) {
127              if (!$plugin->task || preg_match('/('.$plugin->task.')/i', $rcmail->task)) {
128                $plugin->init();
129                $this->plugins[] = $plugin;
130              }
131              $loaded = true;
132            }
133          }
134        }
135      }
136     
137      // trigger fatal error if still not loaded
138      if (!$loaded) {
139        raise_error(array('code' => 520, 'type' => 'php', 'message' => "Requried plugin $plugin_name was not loaded"), true, true);
140      }
141    }
142
143    // register an internal hook
144    $this->register_hook('template_container', array($this, 'template_container_hook'));
145   
146    // maybe also register a shudown function which triggers shutdown functions of all plugin objects
147   
148   
149    // call imap_init right now
150    // (should actually be done in rcmail::imap_init() but plugins are not initialized then)
151    if ($rcmail->imap) {
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'];
155    }
156  }
157 
158 
159  /**
160   * Allows a plugin object to register a callback for a certain hook
161   *
162   * @param string Hook name
163   * @param mixed String with global function name or array($obj, 'methodname')
164   */
165  public function register_hook($hook, $callback)
166  {
167    if (is_callable($callback))
168      $this->handlers[$hook][] = $callback;
169    else
170      raise_error(array('code' => 521, 'type' => 'php', 'message' => "Invalid callback function for $hook"), true, false);
171  }
172 
173 
174  /**
175   * Triggers a plugin hook.
176   * This is called from the application and executes all registered handlers
177   *
178   * @param string Hook name
179   * @param array Named arguments (key->value pairs)
180   * @return array The (probably) altered hook arguments
181   */
182  public function exec_hook($hook, $args = array())
183  {
184    if (!is_array($args))
185      $args = array('arg' => $args);
186
187    $args += array('abort' => false);
188    $this->active_hook = $hook;
189   
190    foreach ((array)$this->handlers[$hook] as $callback) {
191      $ret = call_user_func($callback, $args);
192      if ($ret && is_array($ret))
193        $args = $ret + $args;
194     
195      if ($args['abort'])
196        break;
197    }
198   
199    $this->active_hook = false;
200    return $args;
201  }
202
203
204  /**
205   * Let a plugin register a handler for a specific request
206   *
207   * @param string Action name (_task=mail&_action=plugin.foo)
208   * @param string Plugin name that registers this action
209   * @param mixed Callback: string with global function name or array($obj, 'methodname')
210   */
211  public function register_action($action, $owner, $callback)
212  {
213    // check action name
214    if (strpos($action, 'plugin.') !== 0)
215      $action = 'plugin.'.$action;
216   
217    // can register action only if it's not taken or registered by myself
218    if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) {
219      $this->actions[$action] = $callback;
220      $this->actionmap[$action] = $owner;
221    }
222    else {
223      raise_error(array('code' => 523, 'type' => 'php', 'message' => "Cannot register action $action; already taken by another plugin"), true, false);
224    }
225  }
226
227
228  /**
229   * This method handles requests like _task=mail&_action=plugin.foo
230   * It executes the callback function that was registered with the given action.
231   *
232   * @param string Action name
233   */
234  public function exec_action($action)
235  {
236    if (isset($this->actions[$action])) {
237      call_user_func($this->actions[$action]);
238    }
239    else {
240      raise_error(array('code' => 524, 'type' => 'php', 'message' => "No handler found for action $action"), true, true);
241    }
242  }
243
244
245  /**
246   * Register a handler function for template objects
247   *
248   * @param string Object name
249   * @param string Plugin name that registers this action
250   * @param mixed Callback: string with global function name or array($obj, 'methodname')
251   */
252  public function register_handler($name, $owner, $callback)
253  {
254    // check name
255    if (strpos($name, 'plugin.') !== 0)
256      $name = 'plugin.'.$name;
257   
258    // can register handler only if it's not taken or registered by myself
259    if (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner) {
260      $this->output->add_handler($name, $callback);
261      $this->objectsmap[$name] = $owner;
262    }
263    else {
264      raise_error(array('code' => 525, 'type' => 'php', 'message' => "Cannot register template handler $name; already taken by another plugin"), true, false);
265    }
266  }
267 
268 
269  /**
270   * Check if a plugin hook is currently processing.
271   * Mainly used to prevent loops and recursion.
272   *
273   * @param string Hook to check (optional)
274   * @return boolean True if any/the given hook is currently processed, otherwise false
275   */
276  public function is_processing($hook = null)
277  {
278    return $this->active_hook && (!$hook || $this->active_hook == $hook);
279  }
280 
281  /**
282   * Include a plugin script file in the current HTML page
283   */
284  public function include_script($fn)
285  {
286    if ($this->output->type == 'html') {
287      $src = $this->resource_url($fn);
288      $this->output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $src)));
289    }
290  }
291
292  /**
293   * Include a plugin stylesheet in the current HTML page
294   */
295  public function include_stylesheet($fn)
296  {
297    if ($this->output->type == 'html') {
298      $src = $this->resource_url($fn);
299      $this->output->add_header(html::tag('link', array('rel' => "stylesheet", 'type' => "text/css", 'href' => $src)));
300    }
301  }
302 
303  /**
304   * Save the given HTML content to be added to a template container
305   */
306  public function add_content($html, $container)
307  {
308    $this->template_contents[$container] .= $html . "\n";
309  }
310 
311  /**
312   * Callback for template_container hooks
313   */
314  private function template_container_hook($attrib)
315  {
316    $container = $attrib['name'];
317    return array('content' => $attrib['content'] . $this->template_contents[$container]);
318  }
319 
320  /**
321   * Make the given file name link into the plugins directory
322   */
323  private function resource_url($fn)
324  {
325    if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn))
326      return $this->url . $fn;
327    else
328      return $fn;
329  }
330
331}
332
Note: See TracBrowser for help on using the repository browser.