PageRenderTime 60ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/etc/apps/webmail/program/lib/Roundcube/rcube.php

https://github.com/raiman264/zpanelx
PHP | 1603 lines | 902 code | 261 blank | 440 comment | 225 complexity | 00ea7ab1d9c872b5a17a9c22e3e70249 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-3.0, LGPL-2.1, CC-BY-SA-4.0, GPL-3.0
  1. <?php
  2. /*
  3. +-----------------------------------------------------------------------+
  4. | This file is part of the Roundcube Webmail client |
  5. | Copyright (C) 2008-2012, The Roundcube Dev Team |
  6. | Copyright (C) 2011-2012, Kolab Systems AG |
  7. | |
  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. |
  11. | |
  12. | PURPOSE: |
  13. | Framework base class providing core functions and holding |
  14. | instances of all 'global' objects like db- and storage-connections |
  15. +-----------------------------------------------------------------------+
  16. | Author: Thomas Bruederli <roundcube@gmail.com> |
  17. +-----------------------------------------------------------------------+
  18. */
  19. /**
  20. * Base class of the Roundcube Framework
  21. * implemented as singleton
  22. *
  23. * @package Framework
  24. * @subpackage Core
  25. */
  26. class rcube
  27. {
  28. const INIT_WITH_DB = 1;
  29. const INIT_WITH_PLUGINS = 2;
  30. /**
  31. * Singleton instace of rcube
  32. *
  33. * @var rcube
  34. */
  35. static protected $instance;
  36. /**
  37. * Stores instance of rcube_config.
  38. *
  39. * @var rcube_config
  40. */
  41. public $config;
  42. /**
  43. * Instace of database class.
  44. *
  45. * @var rcube_db
  46. */
  47. public $db;
  48. /**
  49. * Instace of Memcache class.
  50. *
  51. * @var Memcache
  52. */
  53. public $memcache;
  54. /**
  55. * Instace of rcube_session class.
  56. *
  57. * @var rcube_session
  58. */
  59. public $session;
  60. /**
  61. * Instance of rcube_smtp class.
  62. *
  63. * @var rcube_smtp
  64. */
  65. public $smtp;
  66. /**
  67. * Instance of rcube_storage class.
  68. *
  69. * @var rcube_storage
  70. */
  71. public $storage;
  72. /**
  73. * Instance of rcube_output class.
  74. *
  75. * @var rcube_output
  76. */
  77. public $output;
  78. /**
  79. * Instance of rcube_plugin_api.
  80. *
  81. * @var rcube_plugin_api
  82. */
  83. public $plugins;
  84. /* private/protected vars */
  85. protected $texts;
  86. protected $caches = array();
  87. protected $shutdown_functions = array();
  88. /**
  89. * This implements the 'singleton' design pattern
  90. *
  91. * @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants
  92. * @param string Environment name to run (e.g. live, dev, test)
  93. *
  94. * @return rcube The one and only instance
  95. */
  96. static function get_instance($mode = 0, $env = '')
  97. {
  98. if (!self::$instance) {
  99. self::$instance = new rcube($env);
  100. self::$instance->init($mode);
  101. }
  102. return self::$instance;
  103. }
  104. /**
  105. * Private constructor
  106. */
  107. protected function __construct($env = '')
  108. {
  109. // load configuration
  110. $this->config = new rcube_config($env);
  111. $this->plugins = new rcube_dummy_plugin_api;
  112. register_shutdown_function(array($this, 'shutdown'));
  113. }
  114. /**
  115. * Initial startup function
  116. */
  117. protected function init($mode = 0)
  118. {
  119. // initialize syslog
  120. if ($this->config->get('log_driver') == 'syslog') {
  121. $syslog_id = $this->config->get('syslog_id', 'roundcube');
  122. $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
  123. openlog($syslog_id, LOG_ODELAY, $syslog_facility);
  124. }
  125. // connect to database
  126. if ($mode & self::INIT_WITH_DB) {
  127. $this->get_dbh();
  128. }
  129. // create plugin API and load plugins
  130. if ($mode & self::INIT_WITH_PLUGINS) {
  131. $this->plugins = rcube_plugin_api::get_instance();
  132. }
  133. }
  134. /**
  135. * Get the current database connection
  136. *
  137. * @return rcube_db Database object
  138. */
  139. public function get_dbh()
  140. {
  141. if (!$this->db) {
  142. $config_all = $this->config->all();
  143. $this->db = rcube_db::factory($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
  144. $this->db->set_debug((bool)$config_all['sql_debug']);
  145. }
  146. return $this->db;
  147. }
  148. /**
  149. * Get global handle for memcache access
  150. *
  151. * @return object Memcache
  152. */
  153. public function get_memcache()
  154. {
  155. if (!isset($this->memcache)) {
  156. // no memcache support in PHP
  157. if (!class_exists('Memcache')) {
  158. $this->memcache = false;
  159. return false;
  160. }
  161. $this->memcache = new Memcache;
  162. $this->mc_available = 0;
  163. // add all configured hosts to pool
  164. $pconnect = $this->config->get('memcache_pconnect', true);
  165. foreach ($this->config->get('memcache_hosts', array()) as $host) {
  166. if (substr($host, 0, 7) != 'unix://') {
  167. list($host, $port) = explode(':', $host);
  168. if (!$port) $port = 11211;
  169. }
  170. else {
  171. $port = 0;
  172. }
  173. $this->mc_available += intval($this->memcache->addServer(
  174. $host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
  175. }
  176. // test connection and failover (will result in $this->mc_available == 0 on complete failure)
  177. $this->memcache->increment('__CONNECTIONTEST__', 1); // NOP if key doesn't exist
  178. if (!$this->mc_available) {
  179. $this->memcache = false;
  180. }
  181. }
  182. return $this->memcache;
  183. }
  184. /**
  185. * Callback for memcache failure
  186. */
  187. public function memcache_failure($host, $port)
  188. {
  189. static $seen = array();
  190. // only report once
  191. if (!$seen["$host:$port"]++) {
  192. $this->mc_available--;
  193. self::raise_error(array(
  194. 'code' => 604, 'type' => 'db',
  195. 'line' => __LINE__, 'file' => __FILE__,
  196. 'message' => "Memcache failure on host $host:$port"),
  197. true, false);
  198. }
  199. }
  200. /**
  201. * Initialize and get cache object
  202. *
  203. * @param string $name Cache identifier
  204. * @param string $type Cache type ('db', 'apc' or 'memcache')
  205. * @param string $ttl Expiration time for cache items
  206. * @param bool $packed Enables/disables data serialization
  207. *
  208. * @return rcube_cache Cache object
  209. */
  210. public function get_cache($name, $type='db', $ttl=0, $packed=true)
  211. {
  212. if (!isset($this->caches[$name]) && ($userid = $this->get_user_id())) {
  213. $this->caches[$name] = new rcube_cache($type, $userid, $name, $ttl, $packed);
  214. }
  215. return $this->caches[$name];
  216. }
  217. /**
  218. * Initialize and get shared cache object
  219. *
  220. * @param string $name Cache identifier
  221. * @param bool $packed Enables/disables data serialization
  222. *
  223. * @return rcube_cache_shared Cache object
  224. */
  225. public function get_cache_shared($name, $packed=true)
  226. {
  227. $shared_name = "shared_$name";
  228. if (!array_key_exists($shared_name, $this->caches)) {
  229. $opt = strtolower($name) . '_cache';
  230. $type = $this->config->get($opt);
  231. $ttl = $this->config->get($opt . '_ttl');
  232. if (!$type) {
  233. // cache is disabled
  234. return $this->caches[$shared_name] = null;
  235. }
  236. if ($ttl === null) {
  237. $ttl = $this->config->get('shared_cache_ttl', '10d');
  238. }
  239. $this->caches[$shared_name] = new rcube_cache_shared($type, $name, $ttl, $packed);
  240. }
  241. return $this->caches[$shared_name];
  242. }
  243. /**
  244. * Create SMTP object and connect to server
  245. *
  246. * @param boolean True if connection should be established
  247. */
  248. public function smtp_init($connect = false)
  249. {
  250. $this->smtp = new rcube_smtp();
  251. if ($connect) {
  252. $this->smtp->connect();
  253. }
  254. }
  255. /**
  256. * Initialize and get storage object
  257. *
  258. * @return rcube_storage Storage object
  259. */
  260. public function get_storage()
  261. {
  262. // already initialized
  263. if (!is_object($this->storage)) {
  264. $this->storage_init();
  265. }
  266. return $this->storage;
  267. }
  268. /**
  269. * Initialize storage object
  270. */
  271. public function storage_init()
  272. {
  273. // already initialized
  274. if (is_object($this->storage)) {
  275. return;
  276. }
  277. $driver = $this->config->get('storage_driver', 'imap');
  278. $driver_class = "rcube_{$driver}";
  279. if (!class_exists($driver_class)) {
  280. self::raise_error(array(
  281. 'code' => 700, 'type' => 'php',
  282. 'file' => __FILE__, 'line' => __LINE__,
  283. 'message' => "Storage driver class ($driver) not found!"),
  284. true, true);
  285. }
  286. // Initialize storage object
  287. $this->storage = new $driver_class;
  288. // for backward compat. (deprecated, will be removed)
  289. $this->imap = $this->storage;
  290. // enable caching of mail data
  291. $storage_cache = $this->config->get("{$driver}_cache");
  292. $messages_cache = $this->config->get('messages_cache');
  293. // for backward compatybility
  294. if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
  295. $storage_cache = 'db';
  296. $messages_cache = true;
  297. }
  298. if ($storage_cache) {
  299. $this->storage->set_caching($storage_cache);
  300. }
  301. if ($messages_cache) {
  302. $this->storage->set_messages_caching(true);
  303. }
  304. // set pagesize from config
  305. $pagesize = $this->config->get('mail_pagesize');
  306. if (!$pagesize) {
  307. $pagesize = $this->config->get('pagesize', 50);
  308. }
  309. $this->storage->set_pagesize($pagesize);
  310. // set class options
  311. $options = array(
  312. 'auth_type' => $this->config->get("{$driver}_auth_type", 'check'),
  313. 'auth_cid' => $this->config->get("{$driver}_auth_cid"),
  314. 'auth_pw' => $this->config->get("{$driver}_auth_pw"),
  315. 'debug' => (bool) $this->config->get("{$driver}_debug"),
  316. 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"),
  317. 'disabled_caps' => $this->config->get("{$driver}_disabled_caps"),
  318. 'timeout' => (int) $this->config->get("{$driver}_timeout"),
  319. 'skip_deleted' => (bool) $this->config->get('skip_deleted'),
  320. 'driver' => $driver,
  321. );
  322. if (!empty($_SESSION['storage_host'])) {
  323. $options['host'] = $_SESSION['storage_host'];
  324. $options['user'] = $_SESSION['username'];
  325. $options['port'] = $_SESSION['storage_port'];
  326. $options['ssl'] = $_SESSION['storage_ssl'];
  327. $options['password'] = $this->decrypt($_SESSION['password']);
  328. $_SESSION[$driver.'_host'] = $_SESSION['storage_host'];
  329. }
  330. $options = $this->plugins->exec_hook("storage_init", $options);
  331. // for backward compat. (deprecated, to be removed)
  332. $options = $this->plugins->exec_hook("imap_init", $options);
  333. $this->storage->set_options($options);
  334. $this->set_storage_prop();
  335. }
  336. /**
  337. * Set storage parameters.
  338. * This must be done AFTER connecting to the server!
  339. */
  340. protected function set_storage_prop()
  341. {
  342. $storage = $this->get_storage();
  343. $storage->set_charset($this->config->get('default_charset', RCUBE_CHARSET));
  344. if ($default_folders = $this->config->get('default_folders')) {
  345. $storage->set_default_folders($default_folders);
  346. }
  347. if (isset($_SESSION['mbox'])) {
  348. $storage->set_folder($_SESSION['mbox']);
  349. }
  350. if (isset($_SESSION['page'])) {
  351. $storage->set_page($_SESSION['page']);
  352. }
  353. }
  354. /**
  355. * Create session object and start the session.
  356. */
  357. public function session_init()
  358. {
  359. // session started (Installer?)
  360. if (session_id()) {
  361. return;
  362. }
  363. $sess_name = $this->config->get('session_name');
  364. $sess_domain = $this->config->get('session_domain');
  365. $sess_path = $this->config->get('session_path');
  366. $lifetime = $this->config->get('session_lifetime', 0) * 60;
  367. $is_secure = $this->config->get('use_https') || rcube_utils::https_check();
  368. // set session domain
  369. if ($sess_domain) {
  370. ini_set('session.cookie_domain', $sess_domain);
  371. }
  372. // set session path
  373. if ($sess_path) {
  374. ini_set('session.cookie_path', $sess_path);
  375. }
  376. // set session garbage collecting time according to session_lifetime
  377. if ($lifetime) {
  378. ini_set('session.gc_maxlifetime', $lifetime * 2);
  379. }
  380. ini_set('session.cookie_secure', $is_secure);
  381. ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid');
  382. ini_set('session.use_cookies', 1);
  383. ini_set('session.use_only_cookies', 1);
  384. ini_set('session.cookie_httponly', 1);
  385. // use database for storing session data
  386. $this->session = new rcube_session($this->get_dbh(), $this->config);
  387. $this->session->register_gc_handler(array($this, 'gc'));
  388. $this->session->set_secret($this->config->get('des_key') . dirname($_SERVER['SCRIPT_NAME']));
  389. $this->session->set_ip_check($this->config->get('ip_check'));
  390. if ($this->config->get('session_auth_name')) {
  391. $this->session->set_cookiename($this->config->get('session_auth_name'));
  392. }
  393. // start PHP session (if not in CLI mode)
  394. if ($_SERVER['REMOTE_ADDR']) {
  395. $this->session->start();
  396. }
  397. }
  398. /**
  399. * Garbage collector - cache/temp cleaner
  400. */
  401. public function gc()
  402. {
  403. rcube_cache::gc();
  404. rcube_cache_shared::gc();
  405. $this->get_storage()->cache_gc();
  406. $this->gc_temp();
  407. }
  408. /**
  409. * Garbage collector function for temp files.
  410. * Remove temp files older than two days
  411. */
  412. public function gc_temp()
  413. {
  414. $tmp = unslashify($this->config->get('temp_dir'));
  415. // expire in 48 hours by default
  416. $temp_dir_ttl = $this->config->get('temp_dir_ttl', '48h');
  417. $temp_dir_ttl = get_offset_sec($temp_dir_ttl);
  418. if ($temp_dir_ttl < 6*3600)
  419. $temp_dir_ttl = 6*3600; // 6 hours sensible lower bound.
  420. $expire = time() - $temp_dir_ttl;
  421. if ($tmp && ($dir = opendir($tmp))) {
  422. while (($fname = readdir($dir)) !== false) {
  423. if ($fname[0] == '.') {
  424. continue;
  425. }
  426. if (@filemtime($tmp.'/'.$fname) < $expire) {
  427. @unlink($tmp.'/'.$fname);
  428. }
  429. }
  430. closedir($dir);
  431. }
  432. }
  433. /**
  434. * Runs garbage collector with probability based on
  435. * session settings. This is intended for environments
  436. * without a session.
  437. */
  438. public function gc_run()
  439. {
  440. $probability = (int) ini_get('session.gc_probability');
  441. $divisor = (int) ini_get('session.gc_divisor');
  442. if ($divisor > 0 && $probability > 0) {
  443. $random = mt_rand(1, $divisor);
  444. if ($random <= $probability) {
  445. $this->gc();
  446. }
  447. }
  448. }
  449. /**
  450. * Get localized text in the desired language
  451. *
  452. * @param mixed $attrib Named parameters array or label name
  453. * @param string $domain Label domain (plugin) name
  454. *
  455. * @return string Localized text
  456. */
  457. public function gettext($attrib, $domain=null)
  458. {
  459. // load localization files if not done yet
  460. if (empty($this->texts)) {
  461. $this->load_language();
  462. }
  463. // extract attributes
  464. if (is_string($attrib)) {
  465. $attrib = array('name' => $attrib);
  466. }
  467. $name = $attrib['name'] ? $attrib['name'] : '';
  468. // attrib contain text values: use them from now
  469. if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us'])) {
  470. $this->texts[$name] = $setval;
  471. }
  472. // check for text with domain
  473. if ($domain && ($text = $this->texts[$domain.'.'.$name])) {
  474. }
  475. // text does not exist
  476. else if (!($text = $this->texts[$name])) {
  477. return "[$name]";
  478. }
  479. // replace vars in text
  480. if (is_array($attrib['vars'])) {
  481. foreach ($attrib['vars'] as $var_key => $var_value) {
  482. $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
  483. }
  484. }
  485. // format output
  486. if (($attrib['uppercase'] && strtolower($attrib['uppercase'] == 'first')) || $attrib['ucfirst']) {
  487. return ucfirst($text);
  488. }
  489. else if ($attrib['uppercase']) {
  490. return mb_strtoupper($text);
  491. }
  492. else if ($attrib['lowercase']) {
  493. return mb_strtolower($text);
  494. }
  495. return strtr($text, array('\n' => "\n"));
  496. }
  497. /**
  498. * Check if the given text label exists
  499. *
  500. * @param string $name Label name
  501. * @param string $domain Label domain (plugin) name or '*' for all domains
  502. * @param string $ref_domain Sets domain name if label is found
  503. *
  504. * @return boolean True if text exists (either in the current language or in en_US)
  505. */
  506. public function text_exists($name, $domain = null, &$ref_domain = null)
  507. {
  508. // load localization files if not done yet
  509. if (empty($this->texts)) {
  510. $this->load_language();
  511. }
  512. if (isset($this->texts[$name])) {
  513. $ref_domain = '';
  514. return true;
  515. }
  516. // any of loaded domains (plugins)
  517. if ($domain == '*') {
  518. foreach ($this->plugins->loaded_plugins() as $domain) {
  519. if (isset($this->texts[$domain.'.'.$name])) {
  520. $ref_domain = $domain;
  521. return true;
  522. }
  523. }
  524. }
  525. // specified domain
  526. else if ($domain) {
  527. $ref_domain = $domain;
  528. return isset($this->texts[$domain.'.'.$name]);
  529. }
  530. return false;
  531. }
  532. /**
  533. * Load a localization package
  534. *
  535. * @param string $lang Language ID
  536. * @param array $add Additional text labels/messages
  537. * @param array $merge Additional text labels/messages to merge
  538. */
  539. public function load_language($lang = null, $add = array(), $merge = array())
  540. {
  541. $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
  542. // load localized texts
  543. if (empty($this->texts) || $lang != $_SESSION['language']) {
  544. $this->texts = array();
  545. // handle empty lines after closing PHP tag in localization files
  546. ob_start();
  547. // get english labels (these should be complete)
  548. @include(RCUBE_LOCALIZATION_DIR . 'en_US/labels.inc');
  549. @include(RCUBE_LOCALIZATION_DIR . 'en_US/messages.inc');
  550. if (is_array($labels))
  551. $this->texts = $labels;
  552. if (is_array($messages))
  553. $this->texts = array_merge($this->texts, $messages);
  554. // include user language files
  555. if ($lang != 'en' && $lang != 'en_US' && is_dir(RCUBE_LOCALIZATION_DIR . $lang)) {
  556. include_once(RCUBE_LOCALIZATION_DIR . $lang . '/labels.inc');
  557. include_once(RCUBE_LOCALIZATION_DIR . $lang . '/messages.inc');
  558. if (is_array($labels))
  559. $this->texts = array_merge($this->texts, $labels);
  560. if (is_array($messages))
  561. $this->texts = array_merge($this->texts, $messages);
  562. }
  563. ob_end_clean();
  564. $_SESSION['language'] = $lang;
  565. }
  566. // append additional texts (from plugin)
  567. if (is_array($add) && !empty($add)) {
  568. $this->texts += $add;
  569. }
  570. // merge additional texts (from plugin)
  571. if (is_array($merge) && !empty($merge)) {
  572. $this->texts = array_merge($this->texts, $merge);
  573. }
  574. }
  575. /**
  576. * Check the given string and return a valid language code
  577. *
  578. * @param string Language code
  579. *
  580. * @return string Valid language code
  581. */
  582. protected function language_prop($lang)
  583. {
  584. static $rcube_languages, $rcube_language_aliases;
  585. // user HTTP_ACCEPT_LANGUAGE if no language is specified
  586. if (empty($lang) || $lang == 'auto') {
  587. $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
  588. $lang = $accept_langs[0];
  589. if (preg_match('/^([a-z]+)[_-]([a-z]+)$/i', $lang, $m)) {
  590. $lang = $m[1] . '_' . strtoupper($m[2]);
  591. }
  592. }
  593. if (empty($rcube_languages)) {
  594. @include(RCUBE_LOCALIZATION_DIR . 'index.inc');
  595. }
  596. // check if we have an alias for that language
  597. if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
  598. $lang = $rcube_language_aliases[$lang];
  599. }
  600. // try the first two chars
  601. else if (!isset($rcube_languages[$lang])) {
  602. $short = substr($lang, 0, 2);
  603. // check if we have an alias for the short language code
  604. if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
  605. $lang = $rcube_language_aliases[$short];
  606. }
  607. // expand 'nn' to 'nn_NN'
  608. else if (!isset($rcube_languages[$short])) {
  609. $lang = $short.'_'.strtoupper($short);
  610. }
  611. }
  612. if (!isset($rcube_languages[$lang]) || !is_dir(RCUBE_LOCALIZATION_DIR . $lang)) {
  613. $lang = 'en_US';
  614. }
  615. return $lang;
  616. }
  617. /**
  618. * Read directory program/localization and return a list of available languages
  619. *
  620. * @return array List of available localizations
  621. */
  622. public function list_languages()
  623. {
  624. static $sa_languages = array();
  625. if (!sizeof($sa_languages)) {
  626. @include(RCUBE_LOCALIZATION_DIR . 'index.inc');
  627. if ($dh = @opendir(RCUBE_LOCALIZATION_DIR)) {
  628. while (($name = readdir($dh)) !== false) {
  629. if ($name[0] == '.' || !is_dir(RCUBE_LOCALIZATION_DIR . $name)) {
  630. continue;
  631. }
  632. if ($label = $rcube_languages[$name]) {
  633. $sa_languages[$name] = $label;
  634. }
  635. }
  636. closedir($dh);
  637. }
  638. }
  639. return $sa_languages;
  640. }
  641. /**
  642. * Encrypt using 3DES
  643. *
  644. * @param string $clear clear text input
  645. * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
  646. * @param boolean $base64 whether or not to base64_encode() the result before returning
  647. *
  648. * @return string encrypted text
  649. */
  650. public function encrypt($clear, $key = 'des_key', $base64 = true)
  651. {
  652. if (!$clear) {
  653. return '';
  654. }
  655. /*-
  656. * Add a single canary byte to the end of the clear text, which
  657. * will help find out how much of padding will need to be removed
  658. * upon decryption; see http://php.net/mcrypt_generic#68082
  659. */
  660. $clear = pack("a*H2", $clear, "80");
  661. if (function_exists('mcrypt_module_open') &&
  662. ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))
  663. ) {
  664. $iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
  665. mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
  666. $cipher = $iv . mcrypt_generic($td, $clear);
  667. mcrypt_generic_deinit($td);
  668. mcrypt_module_close($td);
  669. }
  670. else {
  671. @include_once 'des.inc';
  672. if (function_exists('des')) {
  673. $des_iv_size = 8;
  674. $iv = $this->create_iv($des_iv_size);
  675. $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
  676. }
  677. else {
  678. self::raise_error(array(
  679. 'code' => 500, 'type' => 'php',
  680. 'file' => __FILE__, 'line' => __LINE__,
  681. 'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
  682. ), true, true);
  683. }
  684. }
  685. return $base64 ? base64_encode($cipher) : $cipher;
  686. }
  687. /**
  688. * Decrypt 3DES-encrypted string
  689. *
  690. * @param string $cipher encrypted text
  691. * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
  692. * @param boolean $base64 whether or not input is base64-encoded
  693. *
  694. * @return string decrypted text
  695. */
  696. public function decrypt($cipher, $key = 'des_key', $base64 = true)
  697. {
  698. if (!$cipher) {
  699. return '';
  700. }
  701. $cipher = $base64 ? base64_decode($cipher) : $cipher;
  702. if (function_exists('mcrypt_module_open') &&
  703. ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))
  704. ) {
  705. $iv_size = mcrypt_enc_get_iv_size($td);
  706. $iv = substr($cipher, 0, $iv_size);
  707. // session corruption? (#1485970)
  708. if (strlen($iv) < $iv_size) {
  709. return '';
  710. }
  711. $cipher = substr($cipher, $iv_size);
  712. mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
  713. $clear = mdecrypt_generic($td, $cipher);
  714. mcrypt_generic_deinit($td);
  715. mcrypt_module_close($td);
  716. }
  717. else {
  718. @include_once 'des.inc';
  719. if (function_exists('des')) {
  720. $des_iv_size = 8;
  721. $iv = substr($cipher, 0, $des_iv_size);
  722. $cipher = substr($cipher, $des_iv_size);
  723. $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
  724. }
  725. else {
  726. self::raise_error(array(
  727. 'code' => 500, 'type' => 'php',
  728. 'file' => __FILE__, 'line' => __LINE__,
  729. 'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
  730. ), true, true);
  731. }
  732. }
  733. /*-
  734. * Trim PHP's padding and the canary byte; see note in
  735. * rcube::encrypt() and http://php.net/mcrypt_generic#68082
  736. */
  737. $clear = substr(rtrim($clear, "\0"), 0, -1);
  738. return $clear;
  739. }
  740. /**
  741. * Generates encryption initialization vector (IV)
  742. *
  743. * @param int Vector size
  744. *
  745. * @return string Vector string
  746. */
  747. private function create_iv($size)
  748. {
  749. // mcrypt_create_iv() can be slow when system lacks entrophy
  750. // we'll generate IV vector manually
  751. $iv = '';
  752. for ($i = 0; $i < $size; $i++) {
  753. $iv .= chr(mt_rand(0, 255));
  754. }
  755. return $iv;
  756. }
  757. /**
  758. * Build a valid URL to this instance of Roundcube
  759. *
  760. * @param mixed Either a string with the action or url parameters as key-value pairs
  761. * @return string Valid application URL
  762. */
  763. public function url($p)
  764. {
  765. // STUB: should be overloaded by the application
  766. return '';
  767. }
  768. /**
  769. * Function to be executed in script shutdown
  770. * Registered with register_shutdown_function()
  771. */
  772. public function shutdown()
  773. {
  774. foreach ($this->shutdown_functions as $function) {
  775. call_user_func($function);
  776. }
  777. // write session data as soon as possible and before
  778. // closing database connection, don't do this before
  779. // registered shutdown functions, they may need the session
  780. // Note: this will run registered gc handlers (ie. cache gc)
  781. if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) {
  782. $this->session->write_close();
  783. }
  784. if (is_object($this->smtp)) {
  785. $this->smtp->disconnect();
  786. }
  787. foreach ($this->caches as $cache) {
  788. if (is_object($cache)) {
  789. $cache->close();
  790. }
  791. }
  792. if (is_object($this->storage)) {
  793. $this->storage->close();
  794. }
  795. }
  796. /**
  797. * Registers shutdown function to be executed on shutdown.
  798. * The functions will be executed before destroying any
  799. * objects like smtp, imap, session, etc.
  800. *
  801. * @param callback Function callback
  802. */
  803. public function add_shutdown_function($function)
  804. {
  805. $this->shutdown_functions[] = $function;
  806. }
  807. /**
  808. * Quote a given string.
  809. * Shortcut function for rcube_utils::rep_specialchars_output()
  810. *
  811. * @return string HTML-quoted string
  812. */
  813. public static function Q($str, $mode = 'strict', $newlines = true)
  814. {
  815. return rcube_utils::rep_specialchars_output($str, 'html', $mode, $newlines);
  816. }
  817. /**
  818. * Quote a given string for javascript output.
  819. * Shortcut function for rcube_utils::rep_specialchars_output()
  820. *
  821. * @return string JS-quoted string
  822. */
  823. public static function JQ($str)
  824. {
  825. return rcube_utils::rep_specialchars_output($str, 'js');
  826. }
  827. /**
  828. * Construct shell command, execute it and return output as string.
  829. * Keywords {keyword} are replaced with arguments
  830. *
  831. * @param $cmd Format string with {keywords} to be replaced
  832. * @param $values (zero, one or more arrays can be passed)
  833. *
  834. * @return output of command. shell errors not detectable
  835. */
  836. public static function exec(/* $cmd, $values1 = array(), ... */)
  837. {
  838. $args = func_get_args();
  839. $cmd = array_shift($args);
  840. $values = $replacements = array();
  841. // merge values into one array
  842. foreach ($args as $arg) {
  843. $values += (array)$arg;
  844. }
  845. preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
  846. foreach ($matches as $tags) {
  847. list(, $tag, $option, $key) = $tags;
  848. $parts = array();
  849. if ($option) {
  850. foreach ((array)$values["-$key"] as $key => $value) {
  851. if ($value === true || $value === false || $value === null) {
  852. $parts[] = $value ? $key : "";
  853. }
  854. else {
  855. foreach ((array)$value as $val) {
  856. $parts[] = "$key " . escapeshellarg($val);
  857. }
  858. }
  859. }
  860. }
  861. else {
  862. foreach ((array)$values[$key] as $value) {
  863. $parts[] = escapeshellarg($value);
  864. }
  865. }
  866. $replacements[$tag] = join(" ", $parts);
  867. }
  868. // use strtr behaviour of going through source string once
  869. $cmd = strtr($cmd, $replacements);
  870. return (string)shell_exec($cmd);
  871. }
  872. /**
  873. * Print or write debug messages
  874. *
  875. * @param mixed Debug message or data
  876. */
  877. public static function console()
  878. {
  879. $args = func_get_args();
  880. if (class_exists('rcube', false)) {
  881. $rcube = self::get_instance();
  882. $plugin = $rcube->plugins->exec_hook('console', array('args' => $args));
  883. if ($plugin['abort']) {
  884. return;
  885. }
  886. $args = $plugin['args'];
  887. }
  888. $msg = array();
  889. foreach ($args as $arg) {
  890. $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
  891. }
  892. self::write_log('console', join(";\n", $msg));
  893. }
  894. /**
  895. * Append a line to a logfile in the logs directory.
  896. * Date will be added automatically to the line.
  897. *
  898. * @param $name name of log file
  899. * @param line Line to append
  900. */
  901. public static function write_log($name, $line)
  902. {
  903. if (!is_string($line)) {
  904. $line = var_export($line, true);
  905. }
  906. $date_format = self::$instance ? self::$instance->config->get('log_date_format') : null;
  907. $log_driver = self::$instance ? self::$instance->config->get('log_driver') : null;
  908. if (empty($date_format)) {
  909. $date_format = 'd-M-Y H:i:s O';
  910. }
  911. $date = date($date_format);
  912. // trigger logging hook
  913. if (is_object(self::$instance) && is_object(self::$instance->plugins)) {
  914. $log = self::$instance->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
  915. $name = $log['name'];
  916. $line = $log['line'];
  917. $date = $log['date'];
  918. if ($log['abort'])
  919. return true;
  920. }
  921. if ($log_driver == 'syslog') {
  922. $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
  923. syslog($prio, $line);
  924. return true;
  925. }
  926. // log_driver == 'file' is assumed here
  927. $line = sprintf("[%s]: %s\n", $date, $line);
  928. $log_dir = null;
  929. // per-user logging is activated
  930. if (self::$instance && self::$instance->config->get('per_user_logging', false) && self::$instance->get_user_id()) {
  931. $log_dir = self::$instance->get_user_log_dir();
  932. if (empty($log_dir))
  933. return false;
  934. }
  935. else if (!empty($log['dir'])) {
  936. $log_dir = $log['dir'];
  937. }
  938. else if (self::$instance) {
  939. $log_dir = self::$instance->config->get('log_dir');
  940. }
  941. if (empty($log_dir)) {
  942. $log_dir = RCUBE_INSTALL_PATH . 'logs';
  943. }
  944. // try to open specific log file for writing
  945. $logfile = $log_dir.'/'.$name;
  946. if ($fp = @fopen($logfile, 'a')) {
  947. fwrite($fp, $line);
  948. fflush($fp);
  949. fclose($fp);
  950. return true;
  951. }
  952. trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
  953. return false;
  954. }
  955. /**
  956. * Throw system error (and show error page).
  957. *
  958. * @param array Named parameters
  959. * - code: Error code (required)
  960. * - type: Error type [php|db|imap|javascript] (required)
  961. * - message: Error message
  962. * - file: File where error occurred
  963. * - line: Line where error occurred
  964. * @param boolean True to log the error
  965. * @param boolean Terminate script execution
  966. */
  967. public static function raise_error($arg = array(), $log = false, $terminate = false)
  968. {
  969. // handle PHP exceptions
  970. if (is_object($arg) && is_a($arg, 'Exception')) {
  971. $arg = array(
  972. 'code' => $arg->getCode(),
  973. 'line' => $arg->getLine(),
  974. 'file' => $arg->getFile(),
  975. 'message' => $arg->getMessage(),
  976. );
  977. }
  978. else if (is_string($arg)) {
  979. $arg = array('message' => $arg);
  980. }
  981. if (empty($arg['code'])) {
  982. $arg['code'] = 500;
  983. }
  984. // installer
  985. if (class_exists('rcube_install', false)) {
  986. $rci = rcube_install::get_instance();
  987. $rci->raise_error($arg);
  988. return;
  989. }
  990. $cli = php_sapi_name() == 'cli';
  991. if (($log || $terminate) && !$cli && $arg['message']) {
  992. $arg['fatal'] = $terminate;
  993. self::log_bug($arg);
  994. }
  995. // terminate script
  996. if ($terminate) {
  997. // display error page
  998. if (is_object(self::$instance->output)) {
  999. self::$instance->output->raise_error($arg['code'], $arg['message']);
  1000. }
  1001. else if ($cli) {
  1002. fwrite(STDERR, 'ERROR: ' . $arg['message']);
  1003. }
  1004. exit(1);
  1005. }
  1006. }
  1007. /**
  1008. * Report error according to configured debug_level
  1009. *
  1010. * @param array Named parameters
  1011. * @see self::raise_error()
  1012. */
  1013. public static function log_bug($arg_arr)
  1014. {
  1015. $program = strtoupper(!empty($arg_arr['type']) ? $arg_arr['type'] : 'php');
  1016. $level = self::get_instance()->config->get('debug_level');
  1017. // disable errors for ajax requests, write to log instead (#1487831)
  1018. if (($level & 4) && !empty($_REQUEST['_remote'])) {
  1019. $level = ($level ^ 4) | 1;
  1020. }
  1021. // write error to local log file
  1022. if (($level & 1) || !empty($arg_arr['fatal'])) {
  1023. if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  1024. $post_query = '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']);
  1025. }
  1026. else {
  1027. $post_query = '';
  1028. }
  1029. $log_entry = sprintf("%s Error: %s%s (%s %s)",
  1030. $program,
  1031. $arg_arr['message'],
  1032. $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
  1033. $_SERVER['REQUEST_METHOD'],
  1034. $_SERVER['REQUEST_URI'] . $post_query);
  1035. if (!self::write_log('errors', $log_entry)) {
  1036. // send error to PHPs error handler if write_log didn't succeed
  1037. trigger_error($arg_arr['message'], E_USER_WARNING);
  1038. }
  1039. }
  1040. // report the bug to the global bug reporting system
  1041. if ($level & 2) {
  1042. // TODO: Send error via HTTP
  1043. }
  1044. // show error if debug_mode is on
  1045. if ($level & 4) {
  1046. print "<b>$program Error";
  1047. if (!empty($arg_arr['file']) && !empty($arg_arr['line'])) {
  1048. print " in $arg_arr[file] ($arg_arr[line])";
  1049. }
  1050. print ':</b>&nbsp;';
  1051. print nl2br($arg_arr['message']);
  1052. print '<br />';
  1053. flush();
  1054. }
  1055. }
  1056. /**
  1057. * Returns current time (with microseconds).
  1058. *
  1059. * @return float Current time in seconds since the Unix
  1060. */
  1061. public static function timer()
  1062. {
  1063. return microtime(true);
  1064. }
  1065. /**
  1066. * Logs time difference according to provided timer
  1067. *
  1068. * @param float $timer Timer (self::timer() result)
  1069. * @param string $label Log line prefix
  1070. * @param string $dest Log file name
  1071. *
  1072. * @see self::timer()
  1073. */
  1074. public static function print_timer($timer, $label = 'Timer', $dest = 'console')
  1075. {
  1076. static $print_count = 0;
  1077. $print_count++;
  1078. $now = self::timer();
  1079. $diff = $now - $timer;
  1080. if (empty($label)) {
  1081. $label = 'Timer '.$print_count;
  1082. }
  1083. self::write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
  1084. }
  1085. /**
  1086. * Getter for logged user ID.
  1087. *
  1088. * @return mixed User identifier
  1089. */
  1090. public function get_user_id()
  1091. {
  1092. if (is_object($this->user)) {
  1093. return $this->user->ID;
  1094. }
  1095. else if (isset($_SESSION['user_id'])) {
  1096. return $_SESSION['user_id'];
  1097. }
  1098. return null;
  1099. }
  1100. /**
  1101. * Getter for logged user name.
  1102. *
  1103. * @return string User name
  1104. */
  1105. public function get_user_name()
  1106. {
  1107. if (is_object($this->user)) {
  1108. return $this->user->get_username();
  1109. }
  1110. else if (isset($_SESSION['username'])) {
  1111. return $_SESSION['username'];
  1112. }
  1113. }
  1114. /**
  1115. * Getter for logged user email (derived from user name not identity).
  1116. *
  1117. * @return string User email address
  1118. */
  1119. public function get_user_email()
  1120. {
  1121. if (is_object($this->user)) {
  1122. return $this->user->get_username('mail');
  1123. }
  1124. }
  1125. /**
  1126. * Getter for logged user password.
  1127. *
  1128. * @return string User password
  1129. */
  1130. public function get_user_password()
  1131. {
  1132. if ($this->password) {
  1133. return $this->password;
  1134. }
  1135. else if ($_SESSION['password']) {
  1136. return $this->decrypt($_SESSION['password']);
  1137. }
  1138. }
  1139. /**
  1140. * Get the per-user log directory
  1141. */
  1142. protected function get_user_log_dir()
  1143. {
  1144. $log_dir = $this->config->get('log_dir', RCUBE_INSTALL_PATH . 'logs');
  1145. $user_name = $this->get_user_name();
  1146. $user_log_dir = $log_dir . '/' . $user_name;
  1147. return !empty($user_name) && is_writable($user_log_dir) ? $user_log_dir : false;
  1148. }
  1149. /**
  1150. * Getter for logged user language code.
  1151. *
  1152. * @return string User language code
  1153. */
  1154. public function get_user_language()
  1155. {
  1156. if (is_object($this->user)) {
  1157. return $this->user->language;
  1158. }
  1159. else if (isset($_SESSION['language'])) {
  1160. return $_SESSION['language'];
  1161. }
  1162. }
  1163. /**
  1164. * Unique Message-ID generator.
  1165. *
  1166. * @return string Message-ID
  1167. */
  1168. public function gen_message_id()
  1169. {
  1170. $local_part = md5(uniqid('rcube'.mt_rand(), true));
  1171. $domain_part = $this->user->get_username('domain');
  1172. // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
  1173. if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
  1174. foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) {
  1175. $host = preg_replace('/:[0-9]+$/', '', $host);
  1176. if ($host && preg_match('/\.[a-z]+$/i', $host)) {
  1177. $domain_part = $host;
  1178. }
  1179. }
  1180. }
  1181. return sprintf('<%s@%s>', $local_part, $domain_part);
  1182. }
  1183. /**
  1184. * Send the given message using the configured method.
  1185. *
  1186. * @param object $message Reference to Mail_MIME object
  1187. * @param string $from Sender address string
  1188. * @param array $mailto Array of recipient address strings
  1189. * @param array $error SMTP error array (reference)
  1190. * @param string $body_file Location of file with saved message body (reference),
  1191. * used when delay_file_io is enabled
  1192. * @param array $options SMTP options (e.g. DSN request)
  1193. *
  1194. * @return boolean Send status.
  1195. */
  1196. public function deliver_message(&$message, $from, $mailto, &$error, &$body_file = null, $options = null)
  1197. {
  1198. $plugin = $this->plugins->exec_hook('message_before_send', array(
  1199. 'message' => $message,
  1200. 'from' => $from,
  1201. 'mailto' => $mailto,
  1202. 'options' => $options,
  1203. ));
  1204. if ($plugin['abort']) {
  1205. return isset($plugin['result']) ? $plugin['result'] : false;
  1206. }
  1207. $from = $plugin['from'];
  1208. $mailto = $plugin['mailto'];
  1209. $options = $plugin['options'];
  1210. $message = $plugin['message'];
  1211. $headers = $message->headers();
  1212. // send thru SMTP server using custom SMTP library
  1213. if ($this->config->get('smtp_server')) {
  1214. // generate list of recipients
  1215. $a_recipients = array($mailto);
  1216. if (strlen($headers['Cc']))
  1217. $a_recipients[] = $headers['Cc'];
  1218. if (strlen($headers['Bcc']))
  1219. $a_recipients[] = $headers['Bcc'];
  1220. // clean Bcc from header for recipients
  1221. $send_headers = $headers;
  1222. unset($send_headers['Bcc']);
  1223. // here too, it because txtHeaders() below use $message->_headers not only $send_headers
  1224. unset($message->_headers['Bcc']);
  1225. $smtp_headers = $message->txtHeaders($send_headers, true);
  1226. if ($message->getParam('delay_file_io')) {
  1227. // use common temp dir
  1228. $temp_dir = $this->config->get('temp_dir');
  1229. $body_file = tempnam($temp_dir, 'rcmMsg');
  1230. if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) {
  1231. self::raise_error(array('code' => 650, 'type' => 'php',
  1232. 'file' => __FILE__, 'line' => __LINE__,
  1233. 'message' => "Could not create message: ".$mime_result->getMessage()),
  1234. TRUE, FALSE);
  1235. return false;
  1236. }
  1237. $msg_body = fopen($body_file, 'r');
  1238. }
  1239. else {
  1240. $msg_body = $message->get();
  1241. }
  1242. // send message
  1243. if (!is_object($this->smtp)) {
  1244. $this->smtp_init(true);
  1245. }
  1246. $sent = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $options);
  1247. $response = $this->smtp->get_response();
  1248. $error = $this->smtp->get_error();
  1249. // log error
  1250. if (!$sent) {
  1251. self::raise_error(array('code' => 800, 'type' => 'smtp',
  1252. 'line' => __LINE__, 'file' => __FILE__,
  1253. 'message' => "SMTP error: ".join("\n", $response)), TRUE, FALSE);
  1254. }
  1255. }
  1256. // send mail using PHP's mail() function
  1257. else {
  1258. // unset some headers because they will be added by the mail() function
  1259. $headers_enc = $message->headers($headers);
  1260. $headers_php = $message->_headers;
  1261. unset($headers_php['To'], $headers_php['Subject']);
  1262. // reset stored headers and overwrite
  1263. $message->_headers = array();
  1264. $header_str = $message->txtHeaders($headers_php);
  1265. // #1485779
  1266. if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
  1267. if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
  1268. $headers_enc['To'] = implode(', ', $m[1]);
  1269. }
  1270. }
  1271. $msg_body = $message->get();
  1272. if (PEAR::isError($msg_body)) {
  1273. self::raise_error(array('code' => 650, 'type' => 'php',
  1274. 'file' => __FILE__, 'line' => __LINE__,
  1275. 'message' => "Could not create message: ".$msg_body->getMessage()),
  1276. TRUE, FALSE);
  1277. }
  1278. else {
  1279. $delim = $this->config->header_delimiter();
  1280. $to = $headers_enc['To'];
  1281. $subject = $headers_enc['Subject'];
  1282. $header_str = rtrim($header_str);
  1283. if ($delim != "\r\n") {
  1284. $header_str = str_replace("\r\n", $delim, $header_str);
  1285. $msg_body = str_replace("\r\n", $delim, $msg_body);
  1286. $to = str_replace("\r\n", $delim, $to);
  1287. $subject = str_replace("\r\n", $delim, $subject);
  1288. }
  1289. if (filter_var(ini_get('safe_mode'), FILTER_VALIDATE_BOOLEAN))
  1290. $sent = mail($to, $subject, $msg_body, $header_str);
  1291. else
  1292. $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
  1293. }
  1294. }
  1295. if ($sent) {
  1296. $this->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body));
  1297. // remove MDN headers after sending
  1298. unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
  1299. // get all recipients
  1300. if ($headers['Cc'])
  1301. $mailto .= $headers['Cc'];
  1302. if ($headers['Bcc'])
  1303. $mailto .= $headers['Bcc'];
  1304. if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
  1305. $mailto = implode(', ', array_unique($m[1]));
  1306. if ($this->config->get('smtp_log')) {
  1307. self::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
  1308. $this->user->get_username(),
  1309. $_SERVER['REMOTE_ADDR'],
  1310. $mailto,
  1311. !empty($response) ? join('; ', $response) : ''));
  1312. }
  1313. }
  1314. else {
  1315. // allow plugins to catch sending errors with the same parameters as in 'message_before_send'
  1316. $this->plugins->exec_hook('message_send_error', $plugin + array('error' => $error));
  1317. }
  1318. if (is_resource($msg_body)) {
  1319. fclose($msg_body);
  1320. }
  1321. $message->_headers = array();
  1322. $message->headers($headers);
  1323. return $sent;
  1324. }
  1325. }
  1326. /**
  1327. * Lightweight plugin API class serving as a dummy if plugins are not enabled
  1328. *
  1329. * @package Framework
  1330. * @subpackage Core
  1331. */
  1332. class rcube_dummy_plugin_api
  1333. {
  1334. /**
  1335. * Triggers a plugin hook.
  1336. * @see rcube_plugin_api::exec_hook()
  1337. */
  1338. public function exec_hook($hook, $args = array())
  1339. {
  1340. return $args;
  1341. }
  1342. }