You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

rcube_plugin_api.php 24KB

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