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