0
|
1 <?php
|
|
2
|
|
3 /**
|
|
4 +-----------------------------------------------------------------------+
|
|
5 | This file is part of the Roundcube Webmail client |
|
|
6 | Copyright (C) 2008-2012, The Roundcube Dev Team |
|
|
7 | |
|
|
8 | Licensed under the GNU General Public License version 3 or |
|
|
9 | any later version with exceptions for skins & plugins. |
|
|
10 | See the README file for a full license statement. |
|
|
11 | |
|
|
12 | PURPOSE: |
|
|
13 | Plugins repository |
|
|
14 +-----------------------------------------------------------------------+
|
|
15 | Author: Thomas Bruederli <roundcube@gmail.com> |
|
|
16 +-----------------------------------------------------------------------+
|
|
17 */
|
|
18
|
|
19 // location where plugins are loade from
|
|
20 if (!defined('RCUBE_PLUGINS_DIR')) {
|
|
21 define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'plugins/');
|
|
22 }
|
|
23
|
|
24 /**
|
|
25 * The plugin loader and global API
|
|
26 *
|
|
27 * @package Framework
|
|
28 * @subpackage PluginAPI
|
|
29 */
|
|
30 class rcube_plugin_api
|
|
31 {
|
|
32 static protected $instance;
|
|
33
|
|
34 public $dir;
|
|
35 public $url = 'plugins/';
|
|
36 public $task = '';
|
|
37 public $initialized = false;
|
|
38
|
|
39 public $output;
|
|
40 public $handlers = array();
|
|
41 public $allowed_prefs = array();
|
|
42 public $allowed_session_prefs = array();
|
|
43 public $active_plugins = array();
|
|
44
|
|
45 protected $plugins = array();
|
|
46 protected $plugins_initialized = array();
|
|
47 protected $tasks = array();
|
|
48 protected $actions = array();
|
|
49 protected $actionmap = array();
|
|
50 protected $objectsmap = array();
|
|
51 protected $template_contents = array();
|
|
52 protected $exec_stack = array();
|
|
53 protected $deprecated_hooks = array();
|
|
54
|
|
55
|
|
56 /**
|
|
57 * This implements the 'singleton' design pattern
|
|
58 *
|
|
59 * @return rcube_plugin_api The one and only instance if this class
|
|
60 */
|
|
61 static function get_instance()
|
|
62 {
|
|
63 if (!self::$instance) {
|
|
64 self::$instance = new rcube_plugin_api();
|
|
65 }
|
|
66
|
|
67 return self::$instance;
|
|
68 }
|
|
69
|
|
70 /**
|
|
71 * Private constructor
|
|
72 */
|
|
73 protected function __construct()
|
|
74 {
|
|
75 $this->dir = slashify(RCUBE_PLUGINS_DIR);
|
|
76 }
|
|
77
|
|
78 /**
|
|
79 * Initialize plugin engine
|
|
80 *
|
|
81 * This has to be done after rcmail::load_gui() or rcmail::json_init()
|
|
82 * was called because plugins need to have access to rcmail->output
|
|
83 *
|
|
84 * @param object rcube Instance of the rcube base class
|
|
85 * @param string Current application task (used for conditional plugin loading)
|
|
86 */
|
|
87 public function init($app, $task = '')
|
|
88 {
|
|
89 $this->task = $task;
|
|
90 $this->output = $app->output;
|
|
91 // register an internal hook
|
|
92 $this->register_hook('template_container', array($this, 'template_container_hook'));
|
|
93 // maybe also register a shudown function which triggers
|
|
94 // shutdown functions of all plugin objects
|
|
95
|
|
96 foreach ($this->plugins as $plugin) {
|
|
97 // ... task, request type and framed mode
|
|
98 if (!$this->plugins_initialized[$plugin->ID] && !$this->filter($plugin)) {
|
|
99 $plugin->init();
|
|
100 $this->plugins_initialized[$plugin->ID] = $plugin;
|
|
101 }
|
|
102 }
|
|
103
|
|
104 // we have finished initializing all plugins
|
|
105 $this->initialized = true;
|
|
106 }
|
|
107
|
|
108 /**
|
|
109 * Load and init all enabled plugins
|
|
110 *
|
|
111 * This has to be done after rcmail::load_gui() or rcmail::json_init()
|
|
112 * was called because plugins need to have access to rcmail->output
|
|
113 *
|
|
114 * @param array List of configured plugins to load
|
|
115 * @param array List of plugins required by the application
|
|
116 */
|
|
117 public function load_plugins($plugins_enabled, $required_plugins = array())
|
|
118 {
|
|
119 foreach ($plugins_enabled as $plugin_name) {
|
|
120 $this->load_plugin($plugin_name);
|
|
121 }
|
|
122
|
|
123 // check existance of all required core plugins
|
|
124 foreach ($required_plugins as $plugin_name) {
|
|
125 $loaded = false;
|
|
126 foreach ($this->plugins as $plugin) {
|
|
127 if ($plugin instanceof $plugin_name) {
|
|
128 $loaded = true;
|
|
129 break;
|
|
130 }
|
|
131 }
|
|
132
|
|
133 // load required core plugin if no derivate was found
|
|
134 if (!$loaded) {
|
|
135 $loaded = $this->load_plugin($plugin_name);
|
|
136 }
|
|
137
|
|
138 // trigger fatal error if still not loaded
|
|
139 if (!$loaded) {
|
|
140 rcube::raise_error(array(
|
|
141 'code' => 520, 'type' => 'php',
|
|
142 'file' => __FILE__, 'line' => __LINE__,
|
|
143 'message' => "Requried plugin $plugin_name was not loaded"), true, true);
|
|
144 }
|
|
145 }
|
|
146 }
|
|
147
|
|
148 /**
|
|
149 * Load the specified plugin
|
|
150 *
|
|
151 * @param string Plugin name
|
|
152 * @param boolean Force loading of the plugin even if it doesn't match the filter
|
|
153 * @param boolean Require loading of the plugin, error if it doesn't exist
|
|
154 *
|
|
155 * @return boolean True on success, false if not loaded or failure
|
|
156 */
|
|
157 public function load_plugin($plugin_name, $force = false, $require = true)
|
|
158 {
|
|
159 static $plugins_dir;
|
|
160
|
|
161 if (!$plugins_dir) {
|
|
162 $dir = dir($this->dir);
|
|
163 $plugins_dir = unslashify($dir->path);
|
|
164 }
|
|
165
|
|
166 // plugin already loaded?
|
|
167 if (!$this->plugins[$plugin_name]) {
|
|
168 $fn = "$plugins_dir/$plugin_name/$plugin_name.php";
|
|
169
|
|
170 if (!is_readable($fn)) {
|
|
171 if ($require) {
|
|
172 rcube::raise_error(array('code' => 520, 'type' => 'php',
|
|
173 'file' => __FILE__, 'line' => __LINE__,
|
|
174 'message' => "Failed to load plugin file $fn"), true, false);
|
|
175 }
|
|
176
|
|
177 return false;
|
|
178 }
|
|
179
|
|
180 if (!class_exists($plugin_name, false)) {
|
|
181 include $fn;
|
|
182 }
|
|
183
|
|
184 // instantiate class if exists
|
|
185 if (!class_exists($plugin_name, false)) {
|
|
186 rcube::raise_error(array('code' => 520, 'type' => 'php',
|
|
187 'file' => __FILE__, 'line' => __LINE__,
|
|
188 'message' => "No plugin class $plugin_name found in $fn"),
|
|
189 true, false);
|
|
190
|
|
191 return false;
|
|
192 }
|
|
193
|
|
194 $plugin = new $plugin_name($this);
|
|
195 $this->active_plugins[] = $plugin_name;
|
|
196
|
|
197 // check inheritance...
|
|
198 if (is_subclass_of($plugin, 'rcube_plugin')) {
|
|
199 // call onload method on plugin if it exists.
|
|
200 // this is useful if you want to be called early in the boot process
|
|
201 if (method_exists($plugin, 'onload')) {
|
|
202 $plugin->onload();
|
|
203 }
|
|
204
|
|
205 if (!empty($plugin->allowed_prefs)) {
|
|
206 $this->allowed_prefs = array_merge($this->allowed_prefs, $plugin->allowed_prefs);
|
|
207 }
|
|
208
|
|
209 $this->plugins[$plugin_name] = $plugin;
|
|
210 }
|
|
211 }
|
|
212
|
|
213 if ($plugin = $this->plugins[$plugin_name]) {
|
|
214 // init a plugin only if $force is set or if we're called after initialization
|
|
215 if (($force || $this->initialized) && !$this->plugins_initialized[$plugin_name] && ($force || !$this->filter($plugin))) {
|
|
216 $plugin->init();
|
|
217 $this->plugins_initialized[$plugin_name] = $plugin;
|
|
218 }
|
|
219 }
|
|
220
|
|
221 return true;
|
|
222 }
|
|
223
|
|
224 /**
|
|
225 * check if we should prevent this plugin from initialising
|
|
226 *
|
|
227 * @param $plugin
|
|
228 * @return bool
|
|
229 */
|
|
230 private function filter($plugin)
|
|
231 {
|
|
232 return ($plugin->noajax && !(is_object($this->output) && $this->output->type == 'html'))
|
|
233 || ($plugin->task && !preg_match('/^('.$plugin->task.')$/i', $this->task))
|
|
234 || ($plugin->noframe && !empty($_REQUEST['_framed']));
|
|
235 }
|
|
236
|
|
237 /**
|
|
238 * Get information about a specific plugin.
|
|
239 * This is either provided my a plugin's info() method or extracted from a package.xml or a composer.json file
|
|
240 *
|
|
241 * @param string Plugin name
|
|
242 * @return array Meta information about a plugin or False if plugin was not found
|
|
243 */
|
|
244 public function get_info($plugin_name)
|
|
245 {
|
|
246 static $composer_lock, $license_uris = array(
|
|
247 'Apache' => 'http://www.apache.org/licenses/LICENSE-2.0.html',
|
|
248 'Apache-2' => 'http://www.apache.org/licenses/LICENSE-2.0.html',
|
|
249 'Apache-1' => 'http://www.apache.org/licenses/LICENSE-1.0',
|
|
250 'Apache-1.1' => 'http://www.apache.org/licenses/LICENSE-1.1',
|
|
251 'GPL' => 'http://www.gnu.org/licenses/gpl.html',
|
|
252 'GPLv2' => 'http://www.gnu.org/licenses/gpl-2.0.html',
|
|
253 'GPL-2.0' => 'http://www.gnu.org/licenses/gpl-2.0.html',
|
|
254 'GPLv3' => 'http://www.gnu.org/licenses/gpl-3.0.html',
|
|
255 'GPLv3+' => 'http://www.gnu.org/licenses/gpl-3.0.html',
|
|
256 'GPL-3.0' => 'http://www.gnu.org/licenses/gpl-3.0.html',
|
|
257 'GPL-3.0+' => 'http://www.gnu.org/licenses/gpl.html',
|
|
258 'GPL-2.0+' => 'http://www.gnu.org/licenses/gpl.html',
|
|
259 'AGPLv3' => 'http://www.gnu.org/licenses/agpl.html',
|
|
260 'AGPLv3+' => 'http://www.gnu.org/licenses/agpl.html',
|
|
261 'AGPL-3.0' => 'http://www.gnu.org/licenses/agpl.html',
|
|
262 'LGPL' => 'http://www.gnu.org/licenses/lgpl.html',
|
|
263 'LGPLv2' => 'http://www.gnu.org/licenses/lgpl-2.0.html',
|
|
264 'LGPLv2.1' => 'http://www.gnu.org/licenses/lgpl-2.1.html',
|
|
265 'LGPLv3' => 'http://www.gnu.org/licenses/lgpl.html',
|
|
266 'LGPL-2.0' => 'http://www.gnu.org/licenses/lgpl-2.0.html',
|
|
267 'LGPL-2.1' => 'http://www.gnu.org/licenses/lgpl-2.1.html',
|
|
268 'LGPL-3.0' => 'http://www.gnu.org/licenses/lgpl.html',
|
|
269 'LGPL-3.0+' => 'http://www.gnu.org/licenses/lgpl.html',
|
|
270 'BSD' => 'http://opensource.org/licenses/bsd-license.html',
|
|
271 'BSD-2-Clause' => 'http://opensource.org/licenses/BSD-2-Clause',
|
|
272 'BSD-3-Clause' => 'http://opensource.org/licenses/BSD-3-Clause',
|
|
273 'FreeBSD' => 'http://opensource.org/licenses/BSD-2-Clause',
|
|
274 'MIT' => 'http://www.opensource.org/licenses/mit-license.php',
|
|
275 'PHP' => 'http://opensource.org/licenses/PHP-3.0',
|
|
276 'PHP-3' => 'http://www.php.net/license/3_01.txt',
|
|
277 'PHP-3.0' => 'http://www.php.net/license/3_0.txt',
|
|
278 'PHP-3.01' => 'http://www.php.net/license/3_01.txt',
|
|
279 );
|
|
280
|
|
281 $dir = dir($this->dir);
|
|
282 $fn = unslashify($dir->path) . "/$plugin_name/$plugin_name.php";
|
|
283 $info = false;
|
|
284
|
|
285 if (!class_exists($plugin_name, false)) {
|
|
286 if (is_readable($fn)) {
|
|
287 include($fn);
|
|
288 }
|
|
289 else {
|
|
290 return false;
|
|
291 }
|
|
292 }
|
|
293
|
|
294 if (class_exists($plugin_name)) {
|
|
295 $info = $plugin_name::info();
|
|
296 }
|
|
297
|
|
298 // fall back to composer.json file
|
|
299 if (!$info) {
|
|
300 $composer = INSTALL_PATH . "/plugins/$plugin_name/composer.json";
|
|
301 if (is_readable($composer) && ($json = @json_decode(file_get_contents($composer), true))) {
|
|
302 list($info['vendor'], $info['name']) = explode('/', $json['name']);
|
|
303 $info['version'] = $json['version'];
|
|
304 $info['license'] = $json['license'];
|
|
305 $info['uri'] = $json['homepage'];
|
|
306 $info['require'] = array_filter(array_keys((array)$json['require']), function($pname) {
|
|
307 if (strpos($pname, '/') == false) {
|
|
308 return false;
|
|
309 }
|
|
310 list($vendor, $name) = explode('/', $pname);
|
|
311 return !($name == 'plugin-installer' || $vendor == 'pear-pear');
|
|
312 });
|
|
313 }
|
|
314
|
|
315 // read local composer.lock file (once)
|
|
316 if (!isset($composer_lock)) {
|
|
317 $composer_lock = @json_decode(@file_get_contents(INSTALL_PATH . "/composer.lock"), true);
|
|
318 if ($composer_lock['packages']) {
|
|
319 foreach ($composer_lock['packages'] as $i => $package) {
|
|
320 $composer_lock['installed'][$package['name']] = $package;
|
|
321 }
|
|
322 }
|
|
323 }
|
|
324
|
|
325 // load additional information from local composer.lock file
|
|
326 if ($lock = $composer_lock['installed'][$json['name']]) {
|
|
327 $info['version'] = $lock['version'];
|
|
328 $info['uri'] = $lock['homepage'] ?: $lock['source']['uri'];
|
|
329 $info['src_uri'] = $lock['dist']['uri'] ?: $lock['source']['uri'];
|
|
330 }
|
|
331 }
|
|
332
|
|
333 // fall back to package.xml file
|
|
334 if (!$info) {
|
|
335 $package = INSTALL_PATH . "/plugins/$plugin_name/package.xml";
|
|
336 if (is_readable($package) && ($file = file_get_contents($package))) {
|
|
337 $doc = new DOMDocument();
|
|
338 $doc->loadXML($file);
|
|
339 $xpath = new DOMXPath($doc);
|
|
340 $xpath->registerNamespace('rc', "http://pear.php.net/dtd/package-2.0");
|
|
341
|
|
342 // XPaths of plugin metadata elements
|
|
343 $metadata = array(
|
|
344 'name' => 'string(//rc:package/rc:name)',
|
|
345 'version' => 'string(//rc:package/rc:version/rc:release)',
|
|
346 'license' => 'string(//rc:package/rc:license)',
|
|
347 'license_uri' => 'string(//rc:package/rc:license/@uri)',
|
|
348 'src_uri' => 'string(//rc:package/rc:srcuri)',
|
|
349 'uri' => 'string(//rc:package/rc:uri)',
|
|
350 );
|
|
351
|
|
352 foreach ($metadata as $key => $path) {
|
|
353 $info[$key] = $xpath->evaluate($path);
|
|
354 }
|
|
355
|
|
356 // dependent required plugins (can be used, but not included in config)
|
|
357 $deps = $xpath->evaluate('//rc:package/rc:dependencies/rc:required/rc:package/rc:name');
|
|
358 for ($i = 0; $i < $deps->length; $i++) {
|
|
359 $dn = $deps->item($i)->nodeValue;
|
|
360 $info['require'][] = $dn;
|
|
361 }
|
|
362 }
|
|
363 }
|
|
364
|
|
365 // At least provide the name
|
|
366 if (!$info && class_exists($plugin_name)) {
|
|
367 $info = array('name' => $plugin_name, 'version' => '--');
|
|
368 }
|
|
369 else if ($info['license'] && empty($info['license_uri']) && ($license_uri = $license_uris[$info['license']])) {
|
|
370 $info['license_uri'] = $license_uri;
|
|
371 }
|
|
372
|
|
373 return $info;
|
|
374 }
|
|
375
|
|
376 /**
|
|
377 * Allows a plugin object to register a callback for a certain hook
|
|
378 *
|
|
379 * @param string $hook Hook name
|
|
380 * @param mixed $callback String with global function name or array($obj, 'methodname')
|
|
381 */
|
|
382 public function register_hook($hook, $callback)
|
|
383 {
|
|
384 if (is_callable($callback)) {
|
|
385 if (isset($this->deprecated_hooks[$hook])) {
|
|
386 rcube::raise_error(array('code' => 522, 'type' => 'php',
|
|
387 'file' => __FILE__, 'line' => __LINE__,
|
|
388 'message' => "Deprecated hook name. "
|
|
389 . $hook . ' -> ' . $this->deprecated_hooks[$hook]), true, false);
|
|
390 $hook = $this->deprecated_hooks[$hook];
|
|
391 }
|
|
392 $this->handlers[$hook][] = $callback;
|
|
393 }
|
|
394 else {
|
|
395 rcube::raise_error(array('code' => 521, 'type' => 'php',
|
|
396 'file' => __FILE__, 'line' => __LINE__,
|
|
397 'message' => "Invalid callback function for $hook"), true, false);
|
|
398 }
|
|
399 }
|
|
400
|
|
401 /**
|
|
402 * Allow a plugin object to unregister a callback.
|
|
403 *
|
|
404 * @param string $hook Hook name
|
|
405 * @param mixed $callback String with global function name or array($obj, 'methodname')
|
|
406 */
|
|
407 public function unregister_hook($hook, $callback)
|
|
408 {
|
|
409 $callback_id = array_search($callback, (array) $this->handlers[$hook]);
|
|
410 if ($callback_id !== false) {
|
|
411 // array_splice() removes the element and re-indexes keys
|
|
412 // that is required by the 'for' loop in exec_hook() below
|
|
413 array_splice($this->handlers[$hook], $callback_id, 1);
|
|
414 }
|
|
415 }
|
|
416
|
|
417 /**
|
|
418 * Triggers a plugin hook.
|
|
419 * This is called from the application and executes all registered handlers
|
|
420 *
|
|
421 * @param string $hook Hook name
|
|
422 * @param array $args Named arguments (key->value pairs)
|
|
423 *
|
|
424 * @return array The (probably) altered hook arguments
|
|
425 */
|
|
426 public function exec_hook($hook, $args = array())
|
|
427 {
|
|
428 if (!is_array($args)) {
|
|
429 $args = array('arg' => $args);
|
|
430 }
|
|
431
|
|
432 // TODO: avoid recursion by checking in_array($hook, $this->exec_stack) ?
|
|
433
|
|
434 $args += array('abort' => false);
|
|
435 array_push($this->exec_stack, $hook);
|
|
436
|
|
437 // Use for loop here, so handlers added in the hook will be executed too
|
|
438 if (!empty($this->handlers[$hook])) {
|
|
439 for ($i = 0; $i < count($this->handlers[$hook]); $i++) {
|
|
440 $ret = call_user_func($this->handlers[$hook][$i], $args);
|
|
441 if ($ret && is_array($ret)) {
|
|
442 $args = $ret + $args;
|
|
443 }
|
|
444
|
|
445 if ($args['break']) {
|
|
446 break;
|
|
447 }
|
|
448 }
|
|
449 }
|
|
450
|
|
451 array_pop($this->exec_stack);
|
|
452 return $args;
|
|
453 }
|
|
454
|
|
455 /**
|
|
456 * Let a plugin register a handler for a specific request
|
|
457 *
|
|
458 * @param string $action Action name (_task=mail&_action=plugin.foo)
|
|
459 * @param string $owner Plugin name that registers this action
|
|
460 * @param mixed $callback Callback: string with global function name or array($obj, 'methodname')
|
|
461 * @param string $task Task name registered by this plugin
|
|
462 */
|
|
463 public function register_action($action, $owner, $callback, $task = null)
|
|
464 {
|
|
465 // check action name
|
|
466 if ($task)
|
|
467 $action = $task.'.'.$action;
|
|
468 else if (strpos($action, 'plugin.') !== 0)
|
|
469 $action = 'plugin.'.$action;
|
|
470
|
|
471 // can register action only if it's not taken or registered by myself
|
|
472 if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) {
|
|
473 $this->actions[$action] = $callback;
|
|
474 $this->actionmap[$action] = $owner;
|
|
475 }
|
|
476 else {
|
|
477 rcube::raise_error(array('code' => 523, 'type' => 'php',
|
|
478 'file' => __FILE__, 'line' => __LINE__,
|
|
479 'message' => "Cannot register action $action;"
|
|
480 ." already taken by another plugin"), true, false);
|
|
481 }
|
|
482 }
|
|
483
|
|
484 /**
|
|
485 * This method handles requests like _task=mail&_action=plugin.foo
|
|
486 * It executes the callback function that was registered with the given action.
|
|
487 *
|
|
488 * @param string $action Action name
|
|
489 */
|
|
490 public function exec_action($action)
|
|
491 {
|
|
492 if (isset($this->actions[$action])) {
|
|
493 call_user_func($this->actions[$action]);
|
|
494 }
|
|
495 else if (rcube::get_instance()->action != 'refresh') {
|
|
496 rcube::raise_error(array('code' => 524, 'type' => 'php',
|
|
497 'file' => __FILE__, 'line' => __LINE__,
|
|
498 'message' => "No handler found for action $action"), true, true);
|
|
499 }
|
|
500 }
|
|
501
|
|
502 /**
|
|
503 * Register a handler function for template objects
|
|
504 *
|
|
505 * @param string $name Object name
|
|
506 * @param string $owner Plugin name that registers this action
|
|
507 * @param mixed $callback Callback: string with global function name or array($obj, 'methodname')
|
|
508 */
|
|
509 public function register_handler($name, $owner, $callback)
|
|
510 {
|
|
511 // check name
|
|
512 if (strpos($name, 'plugin.') !== 0) {
|
|
513 $name = 'plugin.' . $name;
|
|
514 }
|
|
515
|
|
516 // can register handler only if it's not taken or registered by myself
|
|
517 if (is_object($this->output)
|
|
518 && (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner)
|
|
519 ) {
|
|
520 $this->output->add_handler($name, $callback);
|
|
521 $this->objectsmap[$name] = $owner;
|
|
522 }
|
|
523 else {
|
|
524 rcube::raise_error(array('code' => 525, 'type' => 'php',
|
|
525 'file' => __FILE__, 'line' => __LINE__,
|
|
526 'message' => "Cannot register template handler $name;"
|
|
527 ." already taken by another plugin or no output object available"), true, false);
|
|
528 }
|
|
529 }
|
|
530
|
|
531 /**
|
|
532 * Register this plugin to be responsible for a specific task
|
|
533 *
|
|
534 * @param string $task Task name (only characters [a-z0-9_-] are allowed)
|
|
535 * @param string $owner Plugin name that registers this action
|
|
536 */
|
|
537 public function register_task($task, $owner)
|
|
538 {
|
|
539 // tasks are irrelevant in framework mode
|
|
540 if (!class_exists('rcmail', false)) {
|
|
541 return true;
|
|
542 }
|
|
543
|
|
544 if ($task != asciiwords($task, true)) {
|
|
545 rcube::raise_error(array('code' => 526, 'type' => 'php',
|
|
546 'file' => __FILE__, 'line' => __LINE__,
|
|
547 'message' => "Invalid task name: $task."
|
|
548 ." Only characters [a-z0-9_.-] are allowed"), true, false);
|
|
549 }
|
|
550 else if (in_array($task, rcmail::$main_tasks)) {
|
|
551 rcube::raise_error(array('code' => 526, 'type' => 'php',
|
|
552 'file' => __FILE__, 'line' => __LINE__,
|
|
553 'message' => "Cannot register taks $task;"
|
|
554 ." already taken by another plugin or the application itself"), true, false);
|
|
555 }
|
|
556 else {
|
|
557 $this->tasks[$task] = $owner;
|
|
558 rcmail::$main_tasks[] = $task;
|
|
559 return true;
|
|
560 }
|
|
561
|
|
562 return false;
|
|
563 }
|
|
564
|
|
565 /**
|
|
566 * Checks whether the given task is registered by a plugin
|
|
567 *
|
|
568 * @param string $task Task name
|
|
569 *
|
|
570 * @return boolean True if registered, otherwise false
|
|
571 */
|
|
572 public function is_plugin_task($task)
|
|
573 {
|
|
574 return $this->tasks[$task] ? true : false;
|
|
575 }
|
|
576
|
|
577 /**
|
|
578 * Check if a plugin hook is currently processing.
|
|
579 * Mainly used to prevent loops and recursion.
|
|
580 *
|
|
581 * @param string $hook Hook to check (optional)
|
|
582 *
|
|
583 * @return boolean True if any/the given hook is currently processed, otherwise false
|
|
584 */
|
|
585 public function is_processing($hook = null)
|
|
586 {
|
|
587 return count($this->exec_stack) > 0 && (!$hook || in_array($hook, $this->exec_stack));
|
|
588 }
|
|
589
|
|
590 /**
|
|
591 * Include a plugin script file in the current HTML page
|
|
592 *
|
|
593 * @param string $fn Path to script
|
|
594 */
|
|
595 public function include_script($fn)
|
|
596 {
|
|
597 if (is_object($this->output) && $this->output->type == 'html') {
|
|
598 $src = $this->resource_url($fn);
|
|
599 $this->output->add_header(html::tag('script',
|
|
600 array('type' => "text/javascript", 'src' => $src)));
|
|
601 }
|
|
602 }
|
|
603
|
|
604 /**
|
|
605 * Include a plugin stylesheet in the current HTML page
|
|
606 *
|
|
607 * @param string $fn Path to stylesheet
|
|
608 */
|
|
609 public function include_stylesheet($fn)
|
|
610 {
|
|
611 if (is_object($this->output) && $this->output->type == 'html') {
|
|
612 $src = $this->resource_url($fn);
|
|
613 $this->output->include_css($src);
|
|
614 }
|
|
615 }
|
|
616
|
|
617 /**
|
|
618 * Save the given HTML content to be added to a template container
|
|
619 *
|
|
620 * @param string $html HTML content
|
|
621 * @param string $container Template container identifier
|
|
622 */
|
|
623 public function add_content($html, $container)
|
|
624 {
|
|
625 $this->template_contents[$container] .= $html . "\n";
|
|
626 }
|
|
627
|
|
628 /**
|
|
629 * Returns list of loaded plugins names
|
|
630 *
|
|
631 * @return array List of plugin names
|
|
632 */
|
|
633 public function loaded_plugins()
|
|
634 {
|
|
635 return array_keys($this->plugins);
|
|
636 }
|
|
637
|
|
638 /**
|
|
639 * Returns loaded plugin
|
|
640 *
|
|
641 * @return rcube_plugin Plugin instance
|
|
642 */
|
|
643 public function get_plugin($name)
|
|
644 {
|
|
645 return $this->plugins[$name];
|
|
646 }
|
|
647
|
|
648 /**
|
|
649 * Callback for template_container hooks
|
|
650 *
|
|
651 * @param array $attrib
|
|
652 * @return array
|
|
653 */
|
|
654 protected function template_container_hook($attrib)
|
|
655 {
|
|
656 $container = $attrib['name'];
|
|
657 return array('content' => $attrib['content'] . $this->template_contents[$container]);
|
|
658 }
|
|
659
|
|
660 /**
|
|
661 * Make the given file name link into the plugins directory
|
|
662 *
|
|
663 * @param string $fn Filename
|
|
664 * @return string
|
|
665 */
|
|
666 protected function resource_url($fn)
|
|
667 {
|
|
668 if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn))
|
|
669 return $this->url . $fn;
|
|
670 else
|
|
671 return $fn;
|
|
672 }
|
|
673 }
|