source: subversion/branches/devel-api/program/include/rcube_plugin_api.php @ 2285

Last change on this file since 2285 was 2285, checked in by thomasb, 4 years ago

Allow plugins to add stylesheets + use common event interface with event 'init' to start plugin scripts

File size: 8.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 
34  private $handlers = array();
35  private $plugins = array();
36  private $actions = array();
37  private $actionmap = array();
38  private $templobjects = array();
39  private $objectsmap = array();
40  private $scripts = array();
41  private $stylesheets = array();
42  private $output;
43 
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    $rcmail = rcmail::get_instance();
66   
67    // only active in devel_mode for now
68    if (!$rcmail->config->get('devel_mode'))
69      return;
70   
71    $this->dir = realpath($rcmail->config->get('plugins_dir'));
72   
73    // load all enabled plugins
74    $plugins_dir = dir($this->dir);
75    $plugins_enabled = (array)$rcmail->config->get('plugins', array());
76   
77    foreach ($plugins_enabled as $plugin_name) {
78      $fn = $plugins_dir->path . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php';
79     
80      if (file_exists($fn)) {
81        include($fn);
82       
83        // instantiate class if exists
84        if (class_exists($plugin_name, false)) {
85          $plugin = new $plugin_name($this);
86          // check inheritance and task specification
87          if (is_subclass_of($plugin, 'rcube_plugin') && (!$plugin->task || $plugin->task == $rcmail->task)) {
88            $plugin->init();
89            $this->plugins[] = $plugin;
90          }
91        }
92        else {
93          raise_error(array('code' => 520, 'type' => 'php', 'message' => "No plugin class $plugin_name found in $fn"), true, false);
94        }
95      }
96      else {
97        raise_error(array('code' => 520, 'type' => 'php', 'message' => "Failed to load plugin file $fn"), true, false);
98      }
99    }
100   
101    // maybe also register a shudown function which triggers shutdown functions of all plugin objects
102  }
103 
104 
105  /**
106   * Add GUI things once the output objects is created
107   */
108  public function init_gui($output)
109  {
110    if ($output->type == 'html') {
111      $output->add_handlers($this->objectsmap);
112     
113      foreach ($this->stylesheets as $css)
114        $output->add_header(html::tag('link', array('rel' => "stylesheet", 'type' => "text/css", 'href' => $css)));
115     
116      foreach ($this->scripts as $script)
117        $output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $script)));
118    }
119   
120    $this->output = $output;
121  }
122 
123 
124  /**
125   * Allows a plugin object to register a callback for a certain hook
126   *
127   * @param string Hook name
128   * @param mixed String with global function name or array($obj, 'methodname')
129   */
130  public function register_hook($hook, $callback)
131  {
132    if (is_callable($callback))
133      $this->handlers[$hook][] = $callback;
134    else
135      raise_error(array('code' => 521, 'type' => 'php', 'message' => "Invalid callback function for $hook"), true, false);
136  }
137 
138 
139  /**
140   * Triggers a plugin hook.
141   * This is called from the application and executes all registered handlers
142   *
143   * @param string Hook name
144   * @param array Named arguments (key->value pairs)
145   * @return array The (probably) altered hook arguments
146   */
147  public function exec_hook($hook, $args = array())
148  {
149    $args += array('abort' => false);
150   
151    foreach ((array)$this->handlers[$hook] as $callback) {
152      $ret = call_user_func($callback, $args);
153      if ($ret && is_array($ret))
154        $args = $ret + $args;
155     
156      if ($args['abort'])
157        break;
158    }
159   
160    return $args;
161  }
162
163
164  /**
165   * Let a plugin register a handler for a specific request
166   *
167   * @param string Action name (_task=mail&_action=plugin.foo)
168   * @param string Plugin name that registers this action
169   * @param mixed Callback: string with global function name or array($obj, 'methodname')
170   */
171  public function register_action($action, $owner, $callback)
172  {
173    // check action name
174    if (strpos($action, 'plugin.') !== 0)
175      $action = 'plugin.'.$action;
176   
177    // can register action only if it's not taken or registered by myself
178    if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) {
179      $this->actions[$action] = $callback;
180      $this->actionmap[$action] = $owner;
181    }
182    else {
183      raise_error(array('code' => 523, 'type' => 'php', 'message' => "Cannot register action $action; already taken by another plugin"), true, false);
184    }
185  }
186
187
188  /**
189   * This method handles requests like _task=mail&_action=plugin.foo
190   * It executes the callback function that was registered with the given action.
191   *
192   * @param string Action name
193   */
194  public function exec_action($action)
195  {
196    if (isset($this->actions[$action])) {
197      call_user_func($this->actions[$action]);
198    }
199    else {
200      raise_error(array('code' => 524, 'type' => 'php', 'message' => "No handler found for action $action"), true, true);
201    }
202  }
203
204
205  /**
206   * Register a handler function for template objects
207   *
208   * @param string Object name
209   * @param string Plugin name that registers this action
210   * @param mixed Callback: string with global function name or array($obj, 'methodname')
211   */
212  public function register_handler($name, $owner, $callback)
213  {
214    // check name
215    if (strpos($name, 'plugin.') !== 0)
216      $name = 'plugin.'.$name;
217   
218    // can register handler only if it's not taken or registered by myself
219    if (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner) {
220      // output is ready
221      if ($this->output) {
222        $this->output->add_handler($name, $callback);
223      }
224      else {
225        $this->templobjects[$name] = $callback;
226        $this->objectsmap[$name] = $owner;
227      }
228    }
229    else {
230      raise_error(array('code' => 525, 'type' => 'php', 'message' => "Cannot register template handler $name; already taken by another plugin"), true, false);
231    }
232  }
233 
234  /**
235   * Include a plugin script file in the current HTML page
236   */
237  public function include_script($fn)
238  {
239    $src = $this->ressource_url($fn);
240   
241    if ($this->output)
242      $this->output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $src)));
243    else
244      $this->scripts[] = $src;
245  }
246
247  /**
248    * Include a plugin stylesheet in the current HTML page
249   */
250  public function include_stylesheet($fn)
251  {
252    $src = $this->ressource_url($fn);
253   
254    if ($this->output)
255      $this->output->add_header(html::tag('link', array('rel' => "stylesheet", 'type' => "text/css", 'href' => $src)));
256    else
257      $this->stylesheets[] = $src;
258  }
259 
260 
261  /**
262   * Make the given file name link into the plugins directory
263   */
264  private function ressource_url($fn)
265  {
266    if ($fn[0] != '/' && !eregi('^https?://', $fn))
267      return $this->url . $fn;
268    else
269      return $fn;
270  }
271
272}
273
Note: See TracBrowser for help on using the repository browser.