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