Thomas Bruederli
2013-10-07 120db629b0645033fd6a477b9f96cc8dad589213
commit | author | age
cc97ea 1 <?php
T 2
3 /*
4  +-----------------------------------------------------------------------+
e019f2 5  | This file is part of the Roundcube Webmail client                     |
c47813 6  | Copyright (C) 2008-2012, 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      *
97      * @return array Meta information about a plugin or false if not implemented
98      */
99     public static function info()
100     {
101         return false;
102     }
103
104     /**
c47813 105      * Attempt to load the given plugin which is required for the current plugin
AM 106      *
107      * @param string Plugin name
108      * @return boolean True on success, false on failure
109      */
110     public function require_plugin($plugin_name)
111     {
112         return $this->api->load_plugin($plugin_name);
113     }
479af9 114
c47813 115     /**
AM 116      * Load local config file from plugins directory.
117      * The loaded values are patched over the global configuration.
118      *
119      * @param string $fname Config file name relative to the plugin's folder
120      *
121      * @return boolean True on success, false on failure
122      */
123     public function load_config($fname = 'config.inc.php')
124     {
125         $fpath = $this->home.'/'.$fname;
126         $rcube = rcube::get_instance();
479af9 127
c47813 128         if (is_file($fpath) && !$rcube->config->load_from_file($fpath)) {
AM 129             rcube::raise_error(array(
130                 'code' => 527, 'type' => 'php',
131                 'file' => __FILE__, 'line' => __LINE__,
132                 'message' => "Failed to load config from $fpath"), true, false);
133             return false;
59041f 134         }
AM 135
c47813 136         return true;
AM 137     }
138
139     /**
140      * Register a callback function for a specific (server-side) hook
141      *
142      * @param string $hook     Hook name
143      * @param mixed  $callback Callback function as string or array
144      *                         with object reference and method name
145      */
146     public function add_hook($hook, $callback)
147     {
148         $this->api->register_hook($hook, $callback);
149     }
150
151     /**
152      * Unregister a callback function for a specific (server-side) hook.
153      *
154      * @param string $hook     Hook name
155      * @param mixed  $callback Callback function as string or array
156      *                         with object reference and method name
157      */
158     public function remove_hook($hook, $callback)
159     {
160         $this->api->unregister_hook($hook, $callback);
161     }
162
163     /**
164      * Load localized texts from the plugins dir
165      *
166      * @param string $dir        Directory to search in
167      * @param mixed  $add2client Make texts also available on the client
168      *                           (array with list or true for all)
169      */
170     public function add_texts($dir, $add2client = false)
171     {
172         $domain = $this->ID;
173         $lang   = $_SESSION['language'];
174         $langs  = array_unique(array('en_US', $lang));
175         $locdir = slashify(realpath(slashify($this->home) . $dir));
176         $texts  = array();
177
178         // Language aliases used to find localization in similar lang, see below
179         $aliases = array(
180             'de_CH' => 'de_DE',
181             'es_AR' => 'es_ES',
182             'fa_AF' => 'fa_IR',
183             'nl_BE' => 'nl_NL',
184             'pt_BR' => 'pt_PT',
185             'zh_CN' => 'zh_TW',
186         );
187
188         // use buffering to handle empty lines/spaces after closing PHP tag
189         ob_start();
190
191         foreach ($langs as $lng) {
192             $fpath = $locdir . $lng . '.inc';
193             if (is_file($fpath) && is_readable($fpath)) {
194                 include $fpath;
195                 $texts = (array)$labels + (array)$messages + (array)$texts;
196             }
197             else if ($lng != 'en_US') {
198                 // Find localization in similar language (#1488401)
199                 $alias = null;
200                 if (!empty($aliases[$lng])) {
201                     $alias = $aliases[$lng];
202                 }
203                 else if ($key = array_search($lng, $aliases)) {
204                     $alias = $key;
205                 }
206
207                 if (!empty($alias)) {
208                     $fpath = $locdir . $alias . '.inc';
209                     if (is_file($fpath) && is_readable($fpath)) {
210                         include $fpath;
211                         $texts = (array)$labels + (array)$messages + (array)$texts;
212                     }
213                 }
214             }
59041f 215         }
c47813 216
AM 217         ob_end_clean();
218
219         // prepend domain to text keys and add to the application texts repository
220         if (!empty($texts)) {
221             $add = array();
222             foreach ($texts as $key => $value) {
223                 $add[$domain.'.'.$key] = $value;
224             }
225
226             $rcube = rcube::get_instance();
227             $rcube->load_language($lang, $add);
228
229             // add labels to client
52f2a6 230             if ($add2client && method_exists($rcube->output, 'add_label')) {
c47813 231                 if (is_array($add2client)) {
AM 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             }
52f2a6 239         }
AM 240     }
241
242     /**
243      * Wrapper for add_label() adding the plugin ID as domain
244      */
245     public function add_label()
246     {
247         $rcube = rcube::get_instance();
248
249         if (method_exists($rcube->output, 'add_label')) {
250             $args = func_get_args();
251             if (count($args) == 1 && is_array($args[0])) {
252                 $args = $args[0];
253             }
254
255             $args = array_map(array($this, 'label_map_callback'), $args);
256             $rcube->output->add_label($args);
c47813 257         }
cc97ea 258     }
T 259
c47813 260     /**
AM 261      * Wrapper for rcube::gettext() adding the plugin ID as domain
262      *
263      * @param string $p Message identifier
264      *
265      * @return string Localized text
266      * @see rcube::gettext()
267      */
268     public function gettext($p)
269     {
270         return rcube::get_instance()->gettext($p, $this->ID);
9f1652 271     }
TB 272
c47813 273     /**
AM 274      * Register this plugin to be responsible for a specific task
275      *
8b7716 276      * @param string $task Task name (only characters [a-z0-9_-] are allowed)
c47813 277      */
AM 278     public function register_task($task)
279     {
280         if ($this->api->register_task($task, $this->ID)) {
281             $this->mytask = $task;
282         }
283     }
cc97ea 284
c47813 285     /**
AM 286      * Register a handler for a specific client-request action
287      *
288      * The callback will be executed upon a request like /?_task=mail&_action=plugin.myaction
289      *
290      * @param string $action  Action name (should be unique)
291      * @param mixed $callback Callback function as string
292      *                        or array with object reference and method name
293      */
294     public function register_action($action, $callback)
295     {
296         $this->api->register_action($action, $this->ID, $callback, $this->mytask);
297     }
cc97ea 298
c47813 299     /**
AM 300      * Register a handler function for a template object
301      *
302      * When parsing a template for display, tags like <roundcube:object name="plugin.myobject" />
303      * will be replaced by the return value if the registered callback function.
304      *
305      * @param string $name     Object name (should be unique and start with 'plugin.')
306      * @param mixed  $callback Callback function as string or array with object reference
307      *                         and method name
308      */
309     public function register_handler($name, $callback)
310     {
311         $this->api->register_handler($name, $this->ID, $callback);
312     }
313
314     /**
315      * Make this javascipt file available on the client
316      *
317      * @param string $fn File path; absolute or relative to the plugin directory
318      */
319     public function include_script($fn)
320     {
321         $this->api->include_script($this->resource_url($fn));
322     }
323
324     /**
325      * Make this stylesheet available on the client
326      *
327      * @param string $fn File path; absolute or relative to the plugin directory
328      */
329     public function include_stylesheet($fn)
330     {
331         $this->api->include_stylesheet($this->resource_url($fn));
332     }
333
334     /**
335      * Append a button to a certain container
336      *
337      * @param array $p Hash array with named parameters (as used in skin templates)
338      * @param string $container Container name where the buttons should be added to
339      *
340      * @see rcube_remplate::button()
341      */
342     public function add_button($p, $container)
343     {
344         if ($this->api->output->type == 'html') {
345             // fix relative paths
346             foreach (array('imagepas', 'imageact', 'imagesel') as $key) {
347                 if ($p[$key]) {
348                     $p[$key] = $this->api->url . $this->resource_url($p[$key]);
349                 }
350             }
351
352             $this->api->add_content($this->api->output->button($p), $container);
353         }
354     }
355
356     /**
357      * Generate an absolute URL to the given resource within the current
358      * plugin directory
359      *
360      * @param string $fn The file name
361      *
362      * @return string Absolute URL to the given resource
363      */
364     public function url($fn)
365     {
366         return $this->api->url . $this->resource_url($fn);
367     }
368
369     /**
370      * Make the given file name link into the plugin directory
371      *
372      * @param string $fn Filename
373      */
374     private function resource_url($fn)
375     {
376         if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn)) {
377             return $this->ID . '/' . $fn;
378         }
379         else {
380             return $fn;
381         }
382     }
383
384     /**
385      * Provide path to the currently selected skin folder within the plugin directory
386      * with a fallback to the default skin folder.
387      *
388      * @return string Skin path relative to plugins directory
389      */
390     public function local_skin_path()
391     {
392         $rcube = rcube::get_instance();
393         foreach (array($rcube->config->get('skin'), 'larry') as $skin) {
394             $skin_path = 'skins/' . $skin;
395             if (is_dir(realpath(slashify($this->home) . $skin_path))) {
396                 break;
397             }
398         }
399
400         return $skin_path;
401     }
402
403     /**
404      * Callback function for array_map
405      *
406      * @param string $key Array key.
407      * @return string
408      */
409     private function label_map_callback($key)
410     {
52f2a6 411         if (strpos($key, $this->ID.'.') === 0) {
AM 412             return $key;
413         }
414
c47813 415         return $this->ID.'.'.$key;
AM 416     }
cc97ea 417 }