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