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