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.php 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. <?php
  2. /**
  3. +-----------------------------------------------------------------------+
  4. | This file is part of the Roundcube Webmail client |
  5. | Copyright (C) 2008-2014, 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. | Abstract plugins interface/class |
  13. | All plugins need to extend this class |
  14. +-----------------------------------------------------------------------+
  15. | Author: Thomas Bruederli <roundcube@gmail.com> |
  16. +-----------------------------------------------------------------------+
  17. */
  18. /**
  19. * Plugin interface class
  20. *
  21. * @package Framework
  22. * @subpackage PluginAPI
  23. */
  24. abstract class rcube_plugin
  25. {
  26. /**
  27. * Class name of the plugin instance
  28. *
  29. * @var string
  30. */
  31. public $ID;
  32. /**
  33. * Instance of Plugin API
  34. *
  35. * @var rcube_plugin_api
  36. */
  37. public $api;
  38. /**
  39. * Regular expression defining task(s) to bind with
  40. *
  41. * @var string
  42. */
  43. public $task;
  44. /**
  45. * Disables plugin in AJAX requests
  46. *
  47. * @var boolean
  48. */
  49. public $noajax = false;
  50. /**
  51. * Disables plugin in framed mode
  52. *
  53. * @var boolean
  54. */
  55. public $noframe = false;
  56. /**
  57. * A list of config option names that can be modified
  58. * by the user via user interface (with save-prefs command)
  59. *
  60. * @var array
  61. */
  62. public $allowed_prefs;
  63. protected $home;
  64. protected $urlbase;
  65. private $mytask;
  66. private $loaded_config = array();
  67. /**
  68. * Default constructor.
  69. *
  70. * @param rcube_plugin_api $api Plugin API
  71. */
  72. public function __construct($api)
  73. {
  74. $this->ID = get_class($this);
  75. $this->api = $api;
  76. $this->home = $api->dir . $this->ID;
  77. $this->urlbase = $api->url . $this->ID . '/';
  78. }
  79. /**
  80. * Initialization method, needs to be implemented by the plugin itself
  81. */
  82. abstract function init();
  83. /**
  84. * Provide information about this
  85. *
  86. * @return array Meta information about a plugin or false if not implemented:
  87. * As hash array with the following keys:
  88. * name: The plugin name
  89. * vendor: Name of the plugin developer
  90. * version: Plugin version name
  91. * license: License name (short form according to http://spdx.org/licenses/)
  92. * uri: The URL to the plugin homepage or source repository
  93. * src_uri: Direct download URL to the source code of this plugin
  94. * require: List of plugins required for this one (as array of plugin names)
  95. */
  96. public static function info()
  97. {
  98. return false;
  99. }
  100. /**
  101. * Attempt to load the given plugin which is required for the current plugin
  102. *
  103. * @param string Plugin name
  104. * @return boolean True on success, false on failure
  105. */
  106. public function require_plugin($plugin_name)
  107. {
  108. return $this->api->load_plugin($plugin_name, true);
  109. }
  110. /**
  111. * Attempt to load the given plugin which is optional for the current plugin
  112. *
  113. * @param string Plugin name
  114. * @return boolean True on success, false on failure
  115. */
  116. public function include_plugin($plugin_name)
  117. {
  118. return $this->api->load_plugin($plugin_name, true, false);
  119. }
  120. /**
  121. * Load local config file from plugins directory.
  122. * The loaded values are patched over the global configuration.
  123. *
  124. * @param string $fname Config file name relative to the plugin's folder
  125. *
  126. * @return boolean True on success, false on failure
  127. */
  128. public function load_config($fname = 'config.inc.php')
  129. {
  130. if (in_array($fname, $this->loaded_config)) {
  131. return true;
  132. }
  133. $this->loaded_config[] = $fname;
  134. $fpath = $this->home.'/'.$fname;
  135. $rcube = rcube::get_instance();
  136. if (($is_local = is_file($fpath)) && !$rcube->config->load_from_file($fpath)) {
  137. rcube::raise_error(array(
  138. 'code' => 527, 'type' => 'php',
  139. 'file' => __FILE__, 'line' => __LINE__,
  140. 'message' => "Failed to load config from $fpath"), true, false);
  141. return false;
  142. }
  143. else if (!$is_local) {
  144. // Search plugin_name.inc.php file in any configured path
  145. return $rcube->config->load_from_file($this->ID . '.inc.php');
  146. }
  147. return true;
  148. }
  149. /**
  150. * Register a callback function for a specific (server-side) hook
  151. *
  152. * @param string $hook Hook name
  153. * @param mixed $callback Callback function as string or array
  154. * with object reference and method name
  155. */
  156. public function add_hook($hook, $callback)
  157. {
  158. $this->api->register_hook($hook, $callback);
  159. }
  160. /**
  161. * Unregister a callback function for a specific (server-side) hook.
  162. *
  163. * @param string $hook Hook name
  164. * @param mixed $callback Callback function as string or array
  165. * with object reference and method name
  166. */
  167. public function remove_hook($hook, $callback)
  168. {
  169. $this->api->unregister_hook($hook, $callback);
  170. }
  171. /**
  172. * Load localized texts from the plugins dir
  173. *
  174. * @param string $dir Directory to search in
  175. * @param mixed $add2client Make texts also available on the client
  176. * (array with list or true for all)
  177. */
  178. public function add_texts($dir, $add2client = false)
  179. {
  180. $domain = $this->ID;
  181. $lang = $_SESSION['language'];
  182. $langs = array_unique(array('en_US', $lang));
  183. $locdir = slashify(realpath(slashify($this->home) . $dir));
  184. $texts = array();
  185. // Language aliases used to find localization in similar lang, see below
  186. $aliases = array(
  187. 'de_CH' => 'de_DE',
  188. 'es_AR' => 'es_ES',
  189. 'fa_AF' => 'fa_IR',
  190. 'nl_BE' => 'nl_NL',
  191. 'pt_BR' => 'pt_PT',
  192. 'zh_CN' => 'zh_TW',
  193. );
  194. // use buffering to handle empty lines/spaces after closing PHP tag
  195. ob_start();
  196. foreach ($langs as $lng) {
  197. $fpath = $locdir . $lng . '.inc';
  198. if (is_file($fpath) && is_readable($fpath)) {
  199. include $fpath;
  200. $texts = (array)$labels + (array)$messages + (array)$texts;
  201. }
  202. else if ($lng != 'en_US') {
  203. // Find localization in similar language (#1488401)
  204. $alias = null;
  205. if (!empty($aliases[$lng])) {
  206. $alias = $aliases[$lng];
  207. }
  208. else if ($key = array_search($lng, $aliases)) {
  209. $alias = $key;
  210. }
  211. if (!empty($alias)) {
  212. $fpath = $locdir . $alias . '.inc';
  213. if (is_file($fpath) && is_readable($fpath)) {
  214. include $fpath;
  215. $texts = (array)$labels + (array)$messages + (array)$texts;
  216. }
  217. }
  218. }
  219. }
  220. ob_end_clean();
  221. // prepend domain to text keys and add to the application texts repository
  222. if (!empty($texts)) {
  223. $add = array();
  224. foreach ($texts as $key => $value) {
  225. $add[$domain.'.'.$key] = $value;
  226. }
  227. $rcube = rcube::get_instance();
  228. $rcube->load_language($lang, $add);
  229. // add labels to client
  230. if ($add2client && method_exists($rcube->output, 'add_label')) {
  231. if (is_array($add2client)) {
  232. $js_labels = array_map(array($this, 'label_map_callback'), $add2client);
  233. }
  234. else {
  235. $js_labels = array_keys($add);
  236. }
  237. $rcube->output->add_label($js_labels);
  238. }
  239. }
  240. }
  241. /**
  242. * Wrapper for add_label() adding the plugin ID as domain
  243. */
  244. public function add_label()
  245. {
  246. $rcube = rcube::get_instance();
  247. if (method_exists($rcube->output, 'add_label')) {
  248. $args = func_get_args();
  249. if (count($args) == 1 && is_array($args[0])) {
  250. $args = $args[0];
  251. }
  252. $args = array_map(array($this, 'label_map_callback'), $args);
  253. $rcube->output->add_label($args);
  254. }
  255. }
  256. /**
  257. * Wrapper for rcube::gettext() adding the plugin ID as domain
  258. *
  259. * @param string $p Message identifier
  260. *
  261. * @return string Localized text
  262. * @see rcube::gettext()
  263. */
  264. public function gettext($p)
  265. {
  266. return rcube::get_instance()->gettext($p, $this->ID);
  267. }
  268. /**
  269. * Register this plugin to be responsible for a specific task
  270. *
  271. * @param string $task Task name (only characters [a-z0-9_-] are allowed)
  272. */
  273. public function register_task($task)
  274. {
  275. if ($this->api->register_task($task, $this->ID)) {
  276. $this->mytask = $task;
  277. }
  278. }
  279. /**
  280. * Register a handler for a specific client-request action
  281. *
  282. * The callback will be executed upon a request like /?_task=mail&_action=plugin.myaction
  283. *
  284. * @param string $action Action name (should be unique)
  285. * @param mixed $callback Callback function as string
  286. * or array with object reference and method name
  287. */
  288. public function register_action($action, $callback)
  289. {
  290. $this->api->register_action($action, $this->ID, $callback, $this->mytask);
  291. }
  292. /**
  293. * Register a handler function for a template object
  294. *
  295. * When parsing a template for display, tags like <roundcube:object name="plugin.myobject" />
  296. * will be replaced by the return value if the registered callback function.
  297. *
  298. * @param string $name Object name (should be unique and start with 'plugin.')
  299. * @param mixed $callback Callback function as string or array with object reference
  300. * and method name
  301. */
  302. public function register_handler($name, $callback)
  303. {
  304. $this->api->register_handler($name, $this->ID, $callback);
  305. }
  306. /**
  307. * Make this javascipt file available on the client
  308. *
  309. * @param string $fn File path; absolute or relative to the plugin directory
  310. */
  311. public function include_script($fn)
  312. {
  313. $this->api->include_script($this->resource_url($fn));
  314. }
  315. /**
  316. * Make this stylesheet available on the client
  317. *
  318. * @param string $fn File path; absolute or relative to the plugin directory
  319. */
  320. public function include_stylesheet($fn)
  321. {
  322. $this->api->include_stylesheet($this->resource_url($fn));
  323. }
  324. /**
  325. * Append a button to a certain container
  326. *
  327. * @param array $p Hash array with named parameters (as used in skin templates)
  328. * @param string $container Container name where the buttons should be added to
  329. *
  330. * @see rcube_remplate::button()
  331. */
  332. public function add_button($p, $container)
  333. {
  334. if ($this->api->output->type == 'html') {
  335. // fix relative paths
  336. foreach (array('imagepas', 'imageact', 'imagesel') as $key) {
  337. if ($p[$key]) {
  338. $p[$key] = $this->api->url . $this->resource_url($p[$key]);
  339. }
  340. }
  341. $this->api->add_content($this->api->output->button($p), $container);
  342. }
  343. }
  344. /**
  345. * Generate an absolute URL to the given resource within the current
  346. * plugin directory
  347. *
  348. * @param string $fn The file name
  349. *
  350. * @return string Absolute URL to the given resource
  351. */
  352. public function url($fn)
  353. {
  354. return $this->api->url . $this->resource_url($fn);
  355. }
  356. /**
  357. * Make the given file name link into the plugin directory
  358. *
  359. * @param string $fn Filename
  360. */
  361. private function resource_url($fn)
  362. {
  363. if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn)) {
  364. return $this->ID . '/' . $fn;
  365. }
  366. else {
  367. return $fn;
  368. }
  369. }
  370. /**
  371. * Provide path to the currently selected skin folder within the plugin directory
  372. * with a fallback to the default skin folder.
  373. *
  374. * @return string Skin path relative to plugins directory
  375. */
  376. public function local_skin_path()
  377. {
  378. $rcube = rcube::get_instance();
  379. $skins = array_keys((array)$rcube->output->skins);
  380. if (empty($skins)) {
  381. $skins = (array) $rcube->config->get('skin');
  382. }
  383. foreach ($skins as $skin) {
  384. $skin_path = 'skins/' . $skin;
  385. if (is_dir(realpath(slashify($this->home) . $skin_path))) {
  386. break;
  387. }
  388. }
  389. return $skin_path;
  390. }
  391. /**
  392. * Callback function for array_map
  393. *
  394. * @param string $key Array key.
  395. * @return string
  396. */
  397. private function label_map_callback($key)
  398. {
  399. if (strpos($key, $this->ID.'.') === 0) {
  400. return $key;
  401. }
  402. return $this->ID.'.'.$key;
  403. }
  404. }