Mercurial > hg > rc2
annotate program/lib/Roundcube/rcube.php @ 21:73124dd49283
More cleaning up php8 Warnings/deprecations,
noticed that setting non-my-Dates mailboxes to sort on Date isn't sticky over ctrl-R
| author | Charlie Root |
|---|---|
| date | Thu, 09 Oct 2025 11:31:41 -0400 |
| parents | 85a746e95663 |
| children |
| rev | line source |
|---|---|
| 0 | 1 <?php |
| 2 | |
| 3 /** | |
| 4 +-----------------------------------------------------------------------+ | |
| 5 | This file is part of the Roundcube Webmail client | | |
| 6 | Copyright (C) 2008-2014, The Roundcube Dev Team | | |
| 7 | Copyright (C) 2011-2014, Kolab Systems AG | | |
| 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 | Framework base class providing core functions and holding | | |
| 15 | instances of all 'global' objects like db- and storage-connections | | |
| 16 +-----------------------------------------------------------------------+ | |
| 17 | Author: Thomas Bruederli <roundcube@gmail.com> | | |
| 18 +-----------------------------------------------------------------------+ | |
| 19 */ | |
| 20 | |
| 21 /** | |
| 22 * Base class of the Roundcube Framework | |
| 23 * implemented as singleton | |
| 24 * | |
| 25 * @package Framework | |
| 26 * @subpackage Core | |
| 27 */ | |
| 28 class rcube | |
| 29 { | |
| 30 // Init options | |
| 31 const INIT_WITH_DB = 1; | |
| 32 const INIT_WITH_PLUGINS = 2; | |
| 33 | |
| 34 // Request status | |
| 35 const REQUEST_VALID = 0; | |
| 36 const REQUEST_ERROR_URL = 1; | |
| 37 const REQUEST_ERROR_TOKEN = 2; | |
| 38 | |
| 39 const DEBUG_LINE_LENGTH = 4096; | |
| 40 | |
| 41 /** | |
| 42 * Singleton instance of rcube | |
| 43 * | |
| 44 * @var rcube | |
| 45 */ | |
| 46 static protected $instance; | |
| 47 | |
| 48 /** | |
| 49 * Stores instance of rcube_config. | |
| 50 * | |
| 51 * @var rcube_config | |
| 52 */ | |
| 53 public $config; | |
| 54 | |
| 55 /** | |
| 56 * Instance of database class. | |
| 57 * | |
| 58 * @var rcube_db | |
| 59 */ | |
| 60 public $db; | |
| 61 | |
| 62 /** | |
| 63 * Instance of Memcache class. | |
| 64 * | |
| 65 * @var Memcache | |
| 66 */ | |
| 67 public $memcache; | |
| 68 | |
| 69 /** | |
| 70 * Instance of rcube_session class. | |
| 71 * | |
| 72 * @var rcube_session | |
| 73 */ | |
| 74 public $session; | |
| 75 | |
| 76 /** | |
| 77 * Instance of rcube_smtp class. | |
| 78 * | |
| 79 * @var rcube_smtp | |
| 80 */ | |
| 81 public $smtp; | |
| 82 | |
| 83 /** | |
| 84 * Instance of rcube_storage class. | |
| 85 * | |
| 86 * @var rcube_storage | |
| 87 */ | |
| 88 public $storage; | |
| 89 | |
| 90 /** | |
| 91 * Instance of rcube_output class. | |
| 92 * | |
| 93 * @var rcube_output | |
| 94 */ | |
| 95 public $output; | |
| 96 | |
| 97 /** | |
| 98 * Instance of rcube_plugin_api. | |
| 99 * | |
| 100 * @var rcube_plugin_api | |
| 101 */ | |
| 102 public $plugins; | |
| 103 | |
| 104 /** | |
| 105 * Instance of rcube_user class. | |
| 106 * | |
| 107 * @var rcube_user | |
| 108 */ | |
| 109 public $user; | |
| 110 | |
| 111 /** | |
| 112 * Request status | |
| 113 * | |
| 114 * @var int | |
| 115 */ | |
| 116 public $request_status = 0; | |
| 117 | |
| 118 /* private/protected vars */ | |
| 119 protected $texts; | |
| 120 protected $caches = array(); | |
| 121 protected $shutdown_functions = array(); | |
| 122 | |
|
15
85a746e95663
slowly working through deprecations/warnings from 8.3
Charlie Root
parents:
5
diff
changeset
|
123 private $imap; |
| 0 | 124 |
| 125 /** | |
| 126 * This implements the 'singleton' design pattern | |
| 127 * | |
| 128 * @param integer $mode Options to initialize with this instance. See rcube::INIT_WITH_* constants | |
| 129 * @param string $env Environment name to run (e.g. live, dev, test) | |
| 130 * | |
| 131 * @return rcube The one and only instance | |
| 132 */ | |
| 133 static function get_instance($mode = 0, $env = '') | |
| 134 { | |
| 135 if (!self::$instance) { | |
| 136 self::$instance = new rcube($env); | |
| 137 self::$instance->init($mode); | |
| 138 } | |
| 139 | |
| 140 return self::$instance; | |
| 141 } | |
| 142 | |
| 143 /** | |
| 144 * Private constructor | |
| 145 */ | |
| 146 protected function __construct($env = '') | |
| 147 { | |
| 148 // load configuration | |
| 149 $this->config = new rcube_config($env); | |
| 150 $this->plugins = new rcube_dummy_plugin_api; | |
| 151 | |
| 152 register_shutdown_function(array($this, 'shutdown')); | |
| 153 } | |
| 154 | |
| 155 /** | |
| 156 * Initial startup function | |
| 157 */ | |
| 158 protected function init($mode = 0) | |
| 159 { | |
| 160 // initialize syslog | |
| 161 if ($this->config->get('log_driver') == 'syslog') { | |
| 162 $syslog_id = $this->config->get('syslog_id', 'roundcube'); | |
| 163 $syslog_facility = $this->config->get('syslog_facility', LOG_USER); | |
| 164 openlog($syslog_id, LOG_ODELAY, $syslog_facility); | |
| 165 } | |
| 166 | |
| 167 // connect to database | |
| 168 if ($mode & self::INIT_WITH_DB) { | |
| 169 $this->get_dbh(); | |
| 170 } | |
| 171 | |
| 172 // create plugin API and load plugins | |
| 173 if ($mode & self::INIT_WITH_PLUGINS) { | |
| 174 $this->plugins = rcube_plugin_api::get_instance(); | |
| 175 } | |
| 176 } | |
| 177 | |
| 178 /** | |
| 179 * Get the current database connection | |
| 180 * | |
| 181 * @return rcube_db Database object | |
| 182 */ | |
| 183 public function get_dbh() | |
| 184 { | |
| 185 if (!$this->db) { | |
| 186 $this->db = rcube_db::factory( | |
| 187 $this->config->get('db_dsnw'), | |
| 188 $this->config->get('db_dsnr'), | |
| 189 $this->config->get('db_persistent') | |
| 190 ); | |
| 191 | |
| 192 $this->db->set_debug((bool)$this->config->get('sql_debug')); | |
| 193 } | |
| 194 | |
| 195 return $this->db; | |
| 196 } | |
| 197 | |
| 198 /** | |
| 199 * Get global handle for memcache access | |
| 200 * | |
| 201 * @return object Memcache | |
| 202 */ | |
| 203 public function get_memcache() | |
| 204 { | |
| 205 if (!isset($this->memcache)) { | |
| 206 // no memcache support in PHP | |
| 207 if (!class_exists('Memcache')) { | |
| 208 $this->memcache = false; | |
| 209 return false; | |
| 210 } | |
| 211 | |
| 212 $this->memcache = new Memcache; | |
| 213 $this->memcache_init(); | |
| 214 | |
| 215 // test connection and failover (will result in $this->mc_available == 0 on complete failure) | |
| 216 $this->memcache->increment('__CONNECTIONTEST__', 1); // NOP if key doesn't exist | |
| 217 | |
| 218 if (!$this->mc_available) { | |
| 219 $this->memcache = false; | |
| 220 } | |
| 221 } | |
| 222 | |
| 223 return $this->memcache; | |
| 224 } | |
| 225 | |
| 226 /** | |
| 227 * Get global handle for memcache access | |
| 228 * | |
| 229 * @return object Memcache | |
| 230 */ | |
| 231 protected function memcache_init() | |
| 232 { | |
| 233 $this->mc_available = 0; | |
| 234 | |
| 235 // add all configured hosts to pool | |
| 236 $pconnect = $this->config->get('memcache_pconnect', true); | |
| 237 $timeout = $this->config->get('memcache_timeout', 1); | |
| 238 $retry_interval = $this->config->get('memcache_retry_interval', 15); | |
| 239 | |
| 240 foreach ($this->config->get('memcache_hosts', array()) as $host) { | |
| 241 if (substr($host, 0, 7) != 'unix://') { | |
| 242 list($host, $port) = explode(':', $host); | |
| 243 if (!$port) $port = 11211; | |
| 244 } | |
| 245 else { | |
| 246 $port = 0; | |
| 247 } | |
| 248 | |
| 249 $this->mc_available += intval($this->memcache->addServer( | |
| 250 $host, $port, $pconnect, 1, $timeout, $retry_interval, false, array($this, 'memcache_failure'))); | |
| 251 } | |
| 252 } | |
| 253 | |
| 254 /** | |
| 255 * Callback for memcache failure | |
| 256 */ | |
| 257 public function memcache_failure($host, $port) | |
| 258 { | |
| 259 static $seen = array(); | |
| 260 | |
| 261 // only report once | |
| 262 if (!$seen["$host:$port"]++) { | |
| 263 $this->mc_available--; | |
| 264 self::raise_error(array( | |
| 265 'code' => 604, 'type' => 'db', | |
| 266 'line' => __LINE__, 'file' => __FILE__, | |
| 267 'message' => "Memcache failure on host $host:$port"), | |
| 268 true, false); | |
| 269 } | |
| 270 } | |
| 271 | |
| 272 /** | |
| 273 * Initialize and get cache object | |
| 274 * | |
| 275 * @param string $name Cache identifier | |
| 276 * @param string $type Cache type ('db', 'apc' or 'memcache') | |
| 277 * @param string $ttl Expiration time for cache items | |
| 278 * @param bool $packed Enables/disables data serialization | |
| 279 * | |
| 280 * @return rcube_cache Cache object | |
| 281 */ | |
| 282 public function get_cache($name, $type='db', $ttl=0, $packed=true) | |
| 283 { | |
| 284 if (!isset($this->caches[$name]) && ($userid = $this->get_user_id())) { | |
| 285 $this->caches[$name] = new rcube_cache($type, $userid, $name, $ttl, $packed); | |
| 286 } | |
| 287 | |
| 288 return $this->caches[$name]; | |
| 289 } | |
| 290 | |
| 291 /** | |
| 292 * Initialize and get shared cache object | |
| 293 * | |
| 294 * @param string $name Cache identifier | |
| 295 * @param bool $packed Enables/disables data serialization | |
| 296 * | |
| 297 * @return rcube_cache_shared Cache object | |
| 298 */ | |
| 299 public function get_cache_shared($name, $packed=true) | |
| 300 { | |
| 301 $shared_name = "shared_$name"; | |
| 302 | |
| 303 if (!array_key_exists($shared_name, $this->caches)) { | |
| 304 $opt = strtolower($name) . '_cache'; | |
| 305 $type = $this->config->get($opt); | |
| 306 $ttl = $this->config->get($opt . '_ttl'); | |
| 307 | |
| 308 if (!$type) { | |
| 309 // cache is disabled | |
| 310 return $this->caches[$shared_name] = null; | |
| 311 } | |
| 312 | |
| 313 if ($ttl === null) { | |
| 314 $ttl = $this->config->get('shared_cache_ttl', '10d'); | |
| 315 } | |
| 316 | |
| 317 $this->caches[$shared_name] = new rcube_cache_shared($type, $name, $ttl, $packed); | |
| 318 } | |
| 319 | |
| 320 return $this->caches[$shared_name]; | |
| 321 } | |
| 322 | |
| 323 /** | |
| 324 * Create SMTP object and connect to server | |
| 325 * | |
| 326 * @param boolean $connect True if connection should be established | |
| 327 */ | |
| 328 public function smtp_init($connect = false) | |
| 329 { | |
| 330 $this->smtp = new rcube_smtp(); | |
| 331 | |
| 332 if ($connect) { | |
| 333 $this->smtp->connect(); | |
| 334 } | |
| 335 } | |
| 336 | |
| 337 /** | |
| 338 * Initialize and get storage object | |
| 339 * | |
| 340 * @return rcube_storage Storage object | |
| 341 */ | |
| 342 public function get_storage() | |
| 343 { | |
| 344 // already initialized | |
| 345 if (!is_object($this->storage)) { | |
| 346 $this->storage_init(); | |
| 347 } | |
| 348 | |
| 349 return $this->storage; | |
| 350 } | |
| 351 | |
| 352 /** | |
| 353 * Initialize storage object | |
| 354 */ | |
| 355 public function storage_init() | |
| 356 { | |
| 357 // already initialized | |
| 358 if (is_object($this->storage)) { | |
| 359 return; | |
| 360 } | |
| 361 | |
| 362 $driver = $this->config->get('storage_driver', 'imap'); | |
| 363 $driver_class = "rcube_{$driver}"; | |
| 364 | |
| 365 if (!class_exists($driver_class)) { | |
| 366 self::raise_error(array( | |
| 367 'code' => 700, 'type' => 'php', | |
| 368 'file' => __FILE__, 'line' => __LINE__, | |
| 369 'message' => "Storage driver class ($driver) not found!"), | |
| 370 true, true); | |
| 371 } | |
| 372 | |
| 373 // Initialize storage object | |
| 374 $this->storage = new $driver_class; | |
| 375 | |
| 376 // for backward compat. (deprecated, will be removed) | |
| 377 $this->imap = $this->storage; | |
| 378 | |
| 379 // set class options | |
| 380 $options = array( | |
| 381 'auth_type' => $this->config->get("{$driver}_auth_type", 'check'), | |
| 382 'auth_cid' => $this->config->get("{$driver}_auth_cid"), | |
| 383 'auth_pw' => $this->config->get("{$driver}_auth_pw"), | |
| 384 'debug' => (bool) $this->config->get("{$driver}_debug"), | |
| 385 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"), | |
| 386 'disabled_caps' => $this->config->get("{$driver}_disabled_caps"), | |
| 387 'socket_options' => $this->config->get("{$driver}_conn_options"), | |
| 388 'timeout' => (int) $this->config->get("{$driver}_timeout"), | |
| 389 'skip_deleted' => (bool) $this->config->get('skip_deleted'), | |
| 390 'driver' => $driver, | |
| 391 ); | |
| 392 | |
| 393 if (!empty($_SESSION['storage_host'])) { | |
| 394 $options['language'] = $_SESSION['language']; | |
| 395 $options['host'] = $_SESSION['storage_host']; | |
| 396 $options['user'] = $_SESSION['username']; | |
| 397 $options['port'] = $_SESSION['storage_port']; | |
| 398 $options['ssl'] = $_SESSION['storage_ssl']; | |
| 399 $options['password'] = $this->decrypt($_SESSION['password']); | |
| 400 $_SESSION[$driver.'_host'] = $_SESSION['storage_host']; | |
| 401 } | |
| 402 | |
| 403 $options = $this->plugins->exec_hook("storage_init", $options); | |
| 404 | |
| 405 // for backward compat. (deprecated, to be removed) | |
| 406 $options = $this->plugins->exec_hook("imap_init", $options); | |
| 407 | |
| 408 $this->storage->set_options($options); | |
| 409 $this->set_storage_prop(); | |
| 410 | |
| 411 // subscribe to 'storage_connected' hook for session logging | |
| 412 if ($this->config->get('imap_log_session', false)) { | |
| 413 $this->plugins->register_hook('storage_connected', array($this, 'storage_log_session')); | |
| 414 } | |
| 415 } | |
| 416 | |
| 417 /** | |
| 418 * Set storage parameters. | |
| 419 */ | |
| 420 protected function set_storage_prop() | |
| 421 { | |
| 422 $storage = $this->get_storage(); | |
| 423 | |
| 424 // set pagesize from config | |
| 425 $pagesize = $this->config->get('mail_pagesize'); | |
| 426 if (!$pagesize) { | |
| 427 $pagesize = $this->config->get('pagesize', 50); | |
| 428 } | |
| 429 | |
| 430 $storage->set_pagesize($pagesize); | |
| 431 $storage->set_charset($this->config->get('default_charset', RCUBE_CHARSET)); | |
| 432 | |
| 433 // enable caching of mail data | |
| 434 $driver = $this->config->get('storage_driver', 'imap'); | |
| 435 $storage_cache = $this->config->get("{$driver}_cache"); | |
| 436 $messages_cache = $this->config->get('messages_cache'); | |
| 437 // for backward compatybility | |
| 438 if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) { | |
| 439 $storage_cache = 'db'; | |
| 440 $messages_cache = true; | |
| 441 } | |
| 442 | |
| 443 if ($storage_cache) { | |
| 444 $storage->set_caching($storage_cache); | |
| 445 } | |
| 446 if ($messages_cache) { | |
| 447 $storage->set_messages_caching(true); | |
| 448 } | |
| 449 } | |
| 450 | |
| 451 /** | |
| 452 * Set special folders type association. | |
| 453 * This must be done AFTER connecting to the server! | |
| 454 */ | |
| 455 protected function set_special_folders() | |
| 456 { | |
| 457 $storage = $this->get_storage(); | |
| 458 $folders = $storage->get_special_folders(true); | |
| 459 $prefs = array(); | |
| 460 | |
| 461 // check SPECIAL-USE flags on IMAP folders | |
| 462 foreach ($folders as $type => $folder) { | |
| 463 $idx = $type . '_mbox'; | |
| 464 if ($folder !== $this->config->get($idx)) { | |
| 465 $prefs[$idx] = $folder; | |
| 466 } | |
| 467 } | |
| 468 | |
| 469 // Some special folders differ, update user preferences | |
| 470 if (!empty($prefs) && $this->user) { | |
| 471 $this->user->save_prefs($prefs); | |
| 472 } | |
| 473 | |
| 474 // create default folders (on login) | |
| 475 if ($this->config->get('create_default_folders')) { | |
| 476 $storage->create_default_folders(); | |
| 477 } | |
| 478 } | |
| 479 | |
| 480 /** | |
| 481 * Callback for IMAP connection events to log session identifiers | |
| 482 */ | |
| 483 public function storage_log_session($args) | |
| 484 { | |
| 485 if (!empty($args['session']) && session_id()) { | |
| 486 $this->write_log('imap_session', $args['session']); | |
| 487 } | |
| 488 } | |
| 489 | |
| 490 /** | |
| 491 * Create session object and start the session. | |
| 492 */ | |
| 493 public function session_init() | |
| 494 { | |
| 495 // session started (Installer?) | |
| 496 if (session_id()) { | |
| 497 return; | |
| 498 } | |
| 499 | |
| 500 $sess_name = $this->config->get('session_name'); | |
| 501 $sess_domain = $this->config->get('session_domain'); | |
| 502 $sess_path = $this->config->get('session_path'); | |
| 503 $lifetime = $this->config->get('session_lifetime', 0) * 60; | |
| 504 $is_secure = $this->config->get('use_https') || rcube_utils::https_check(); | |
| 505 | |
| 506 // set session domain | |
| 507 if ($sess_domain) { | |
| 508 ini_set('session.cookie_domain', $sess_domain); | |
| 509 } | |
| 510 // set session path | |
| 511 if ($sess_path) { | |
| 512 ini_set('session.cookie_path', $sess_path); | |
| 513 } | |
| 514 // set session garbage collecting time according to session_lifetime | |
| 515 if ($lifetime) { | |
| 516 ini_set('session.gc_maxlifetime', $lifetime * 2); | |
| 517 } | |
| 518 | |
| 519 // set session cookie lifetime so it never expires (#5961) | |
| 520 ini_set('session.cookie_lifetime', 0); | |
| 521 ini_set('session.cookie_secure', $is_secure); | |
| 522 ini_set('session.name', $sess_name ?: 'roundcube_sessid'); | |
| 523 ini_set('session.use_cookies', 1); | |
| 524 ini_set('session.use_only_cookies', 1); | |
| 525 ini_set('session.cookie_httponly', 1); | |
| 526 | |
| 527 // get session driver instance | |
| 528 $this->session = rcube_session::factory($this->config); | |
| 529 $this->session->register_gc_handler(array($this, 'gc')); | |
| 530 | |
| 531 // start PHP session (if not in CLI mode) | |
| 532 if ($_SERVER['REMOTE_ADDR']) { | |
| 533 $this->session->start(); | |
| 534 } | |
| 535 } | |
| 536 | |
| 537 /** | |
| 538 * Garbage collector - cache/temp cleaner | |
| 539 */ | |
| 540 public function gc() | |
| 541 { | |
| 542 rcube_cache::gc(); | |
| 543 rcube_cache_shared::gc(); | |
| 544 $this->get_storage()->cache_gc(); | |
| 545 | |
| 546 $this->gc_temp(); | |
| 547 } | |
| 548 | |
| 549 /** | |
| 550 * Garbage collector function for temp files. | |
| 551 * Remove temp files older than two days | |
| 552 */ | |
| 553 public function gc_temp() | |
| 554 { | |
| 555 $tmp = unslashify($this->config->get('temp_dir')); | |
| 556 | |
| 557 // expire in 48 hours by default | |
| 558 $temp_dir_ttl = $this->config->get('temp_dir_ttl', '48h'); | |
| 559 $temp_dir_ttl = get_offset_sec($temp_dir_ttl); | |
| 560 if ($temp_dir_ttl < 6*3600) | |
| 561 $temp_dir_ttl = 6*3600; // 6 hours sensible lower bound. | |
| 562 | |
| 563 $expire = time() - $temp_dir_ttl; | |
| 564 | |
| 565 if ($tmp && ($dir = opendir($tmp))) { | |
| 566 while (($fname = readdir($dir)) !== false) { | |
| 567 if ($fname[0] == '.') { | |
| 568 continue; | |
| 569 } | |
| 570 | |
| 571 if (@filemtime($tmp.'/'.$fname) < $expire) { | |
| 572 @unlink($tmp.'/'.$fname); | |
| 573 } | |
| 574 } | |
| 575 | |
| 576 closedir($dir); | |
| 577 } | |
| 578 } | |
| 579 | |
| 580 /** | |
| 581 * Runs garbage collector with probability based on | |
| 582 * session settings. This is intended for environments | |
| 583 * without a session. | |
| 584 */ | |
| 585 public function gc_run() | |
| 586 { | |
| 587 $probability = (int) ini_get('session.gc_probability'); | |
| 588 $divisor = (int) ini_get('session.gc_divisor'); | |
| 589 | |
| 590 if ($divisor > 0 && $probability > 0) { | |
| 591 $random = mt_rand(1, $divisor); | |
| 592 if ($random <= $probability) { | |
| 593 $this->gc(); | |
| 594 } | |
| 595 } | |
| 596 } | |
| 597 | |
| 598 /** | |
| 599 * Get localized text in the desired language | |
| 600 * | |
| 601 * @param mixed $attrib Named parameters array or label name | |
| 602 * @param string $domain Label domain (plugin) name | |
| 603 * | |
| 604 * @return string Localized text | |
| 605 */ | |
| 606 public function gettext($attrib, $domain = null) | |
| 607 { | |
| 608 // load localization files if not done yet | |
| 609 if (empty($this->texts)) { | |
| 610 $this->load_language(); | |
| 611 } | |
| 612 | |
| 613 // extract attributes | |
| 614 if (is_string($attrib)) { | |
| 615 $attrib = array('name' => $attrib); | |
| 616 } | |
| 617 | |
| 618 $name = (string) $attrib['name']; | |
| 619 | |
| 620 // attrib contain text values: use them from now | |
|
15
85a746e95663
slowly working through deprecations/warnings from 8.3
Charlie Root
parents:
5
diff
changeset
|
621 if (($setval = $attrib[strtolower($_SESSION['language'])] ?? false) || ($setval = $attrib['en_us'] ?? false)) { |
| 0 | 622 $this->texts[$name] = $setval; |
| 623 } | |
| 624 // check for text with domain | |
| 625 if ($domain && ($text = $this->texts[$domain.'.'.$name])) { | |
| 626 } | |
| 627 // text does not exist | |
| 21 | 628 else if (!($text = ($this->texts[$name]??null))) { |
| 0 | 629 return "[$name]"; |
| 630 } | |
| 631 // replace vars in text | |
|
15
85a746e95663
slowly working through deprecations/warnings from 8.3
Charlie Root
parents:
5
diff
changeset
|
632 if (is_array($attrib['vars'] ?? false)) { |
| 0 | 633 foreach ($attrib['vars'] as $var_key => $var_value) { |
| 634 $text = str_replace($var_key[0] != '$' ? '$'.$var_key : $var_key, $var_value, $text); | |
| 635 } | |
| 636 } | |
| 637 | |
| 638 // replace \n with real line break | |
| 639 $text = strtr($text, array('\n' => "\n")); | |
| 640 | |
| 641 // case folding | |
|
15
85a746e95663
slowly working through deprecations/warnings from 8.3
Charlie Root
parents:
5
diff
changeset
|
642 if ((($attrib['uppercase'] ?? false) && strtolower($attrib['uppercase']) == 'first') || ($attrib['ucfirst'] ?? false)) { |
| 0 | 643 $case_mode = MB_CASE_TITLE; |
| 644 } | |
|
15
85a746e95663
slowly working through deprecations/warnings from 8.3
Charlie Root
parents:
5
diff
changeset
|
645 else if ($attrib['uppercase'] ?? false) { |
| 0 | 646 $case_mode = MB_CASE_UPPER; |
| 647 } | |
|
15
85a746e95663
slowly working through deprecations/warnings from 8.3
Charlie Root
parents:
5
diff
changeset
|
648 else if ($attrib['lowercase'] ?? false) { |
| 0 | 649 $case_mode = MB_CASE_LOWER; |
| 650 } | |
| 651 | |
| 652 if (isset($case_mode)) { | |
| 653 $text = mb_convert_case($text, $case_mode); | |
| 654 } | |
| 655 return $text; | |
| 656 } | |
| 657 | |
| 658 /** | |
| 659 * Check if the given text label exists | |
| 660 * | |
| 661 * @param string $name Label name | |
| 662 * @param string $domain Label domain (plugin) name or '*' for all domains | |
| 663 * @param string $ref_domain Sets domain name if label is found | |
| 664 * | |
| 665 * @return boolean True if text exists (either in the current language or in en_US) | |
| 666 */ | |
| 667 public function text_exists($name, $domain = null, &$ref_domain = null) | |
| 668 { | |
| 669 // load localization files if not done yet | |
| 670 if (empty($this->texts)) { | |
| 671 $this->load_language(); | |
| 672 } | |
| 673 | |
| 674 if (isset($this->texts[$name])) { | |
| 675 $ref_domain = ''; | |
| 676 return true; | |
| 677 } | |
| 678 | |
| 679 // any of loaded domains (plugins) | |
| 680 if ($domain == '*') { | |
| 681 foreach ($this->plugins->loaded_plugins() as $domain) { | |
| 682 if (isset($this->texts[$domain.'.'.$name])) { | |
| 683 $ref_domain = $domain; | |
| 684 return true; | |
| 685 } | |
| 686 } | |
| 687 } | |
| 688 // specified domain | |
| 689 else if ($domain) { | |
| 690 $ref_domain = $domain; | |
| 691 return isset($this->texts[$domain.'.'.$name]); | |
| 692 } | |
| 693 | |
| 694 return false; | |
| 695 } | |
| 696 | |
| 697 /** | |
| 698 * Load a localization package | |
| 699 * | |
| 700 * @param string $lang Language ID | |
| 701 * @param array $add Additional text labels/messages | |
| 702 * @param array $merge Additional text labels/messages to merge | |
| 703 */ | |
| 704 public function load_language($lang = null, $add = array(), $merge = array()) | |
| 705 { | |
| 706 $lang = $this->language_prop($lang ?: $_SESSION['language']); | |
| 707 | |
| 708 // load localized texts | |
| 709 if (empty($this->texts) || $lang != $_SESSION['language']) { | |
| 710 $this->texts = array(); | |
| 711 | |
| 712 // handle empty lines after closing PHP tag in localization files | |
| 713 ob_start(); | |
| 714 | |
| 715 // get english labels (these should be complete) | |
| 716 @include(RCUBE_LOCALIZATION_DIR . 'en_US/labels.inc'); | |
| 717 @include(RCUBE_LOCALIZATION_DIR . 'en_US/messages.inc'); | |
| 718 | |
| 719 if (is_array($labels)) | |
| 720 $this->texts = $labels; | |
| 721 if (is_array($messages)) | |
| 722 $this->texts = array_merge($this->texts, $messages); | |
| 723 | |
| 724 // include user language files | |
| 725 if ($lang != 'en' && $lang != 'en_US' && is_dir(RCUBE_LOCALIZATION_DIR . $lang)) { | |
| 726 include_once(RCUBE_LOCALIZATION_DIR . $lang . '/labels.inc'); | |
| 727 include_once(RCUBE_LOCALIZATION_DIR . $lang . '/messages.inc'); | |
| 728 | |
| 729 if (is_array($labels)) | |
| 730 $this->texts = array_merge($this->texts, $labels); | |
| 731 if (is_array($messages)) | |
| 732 $this->texts = array_merge($this->texts, $messages); | |
| 733 } | |
| 734 | |
| 735 ob_end_clean(); | |
| 736 | |
| 737 $_SESSION['language'] = $lang; | |
| 738 } | |
| 739 | |
| 740 // append additional texts (from plugin) | |
| 741 if (is_array($add) && !empty($add)) { | |
| 742 $this->texts += $add; | |
| 743 } | |
| 744 | |
| 745 // merge additional texts (from plugin) | |
| 746 if (is_array($merge) && !empty($merge)) { | |
| 747 $this->texts = array_merge($this->texts, $merge); | |
| 748 } | |
| 749 } | |
| 750 | |
| 751 /** | |
| 752 * Check the given string and return a valid language code | |
| 753 * | |
| 754 * @param string $lang Language code | |
| 755 * | |
| 756 * @return string Valid language code | |
| 757 */ | |
| 758 protected function language_prop($lang) | |
| 759 { | |
| 760 static $rcube_languages, $rcube_language_aliases; | |
| 761 | |
| 762 // user HTTP_ACCEPT_LANGUAGE if no language is specified | |
| 763 if (empty($lang) || $lang == 'auto') { | |
| 764 $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); | |
| 765 $lang = $accept_langs[0]; | |
| 766 | |
| 767 if (preg_match('/^([a-z]+)[_-]([a-z]+)$/i', $lang, $m)) { | |
| 768 $lang = $m[1] . '_' . strtoupper($m[2]); | |
| 769 } | |
| 770 } | |
| 771 | |
| 772 if (empty($rcube_languages)) { | |
| 773 @include(RCUBE_LOCALIZATION_DIR . 'index.inc'); | |
| 774 } | |
| 775 | |
| 776 // check if we have an alias for that language | |
| 777 if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) { | |
| 778 $lang = $rcube_language_aliases[$lang]; | |
| 779 } | |
| 780 // try the first two chars | |
| 781 else if (!isset($rcube_languages[$lang])) { | |
| 782 $short = substr($lang, 0, 2); | |
| 783 | |
| 784 // check if we have an alias for the short language code | |
| 785 if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) { | |
| 786 $lang = $rcube_language_aliases[$short]; | |
| 787 } | |
| 788 // expand 'nn' to 'nn_NN' | |
| 789 else if (!isset($rcube_languages[$short])) { | |
| 790 $lang = $short.'_'.strtoupper($short); | |
| 791 } | |
| 792 } | |
| 793 | |
| 794 if (!isset($rcube_languages[$lang]) || !is_dir(RCUBE_LOCALIZATION_DIR . $lang)) { | |
| 795 $lang = 'en_US'; | |
| 796 } | |
| 797 | |
| 798 return $lang; | |
| 799 } | |
| 800 | |
| 801 /** | |
| 802 * Read directory program/localization and return a list of available languages | |
| 803 * | |
| 804 * @return array List of available localizations | |
| 805 */ | |
| 806 public function list_languages() | |
| 807 { | |
| 808 static $sa_languages = array(); | |
| 809 | |
| 810 if (!count($sa_languages)) { | |
| 811 @include(RCUBE_LOCALIZATION_DIR . 'index.inc'); | |
| 812 | |
| 813 if ($dh = @opendir(RCUBE_LOCALIZATION_DIR)) { | |
| 814 while (($name = readdir($dh)) !== false) { | |
| 815 if ($name[0] == '.' || !is_dir(RCUBE_LOCALIZATION_DIR . $name)) { | |
| 816 continue; | |
| 817 } | |
| 818 | |
| 819 if ($label = $rcube_languages[$name]) { | |
| 820 $sa_languages[$name] = $label; | |
| 821 } | |
| 822 } | |
| 823 closedir($dh); | |
| 824 } | |
| 825 } | |
| 826 | |
| 827 return $sa_languages; | |
| 828 } | |
| 829 | |
| 830 /** | |
| 831 * Encrypt a string | |
| 832 * | |
| 833 * @param string $clear Clear text input | |
| 834 * @param string $key Encryption key to retrieve from the configuration, defaults to 'des_key' | |
| 835 * @param boolean $base64 Whether or not to base64_encode() the result before returning | |
| 836 * | |
| 837 * @return string Encrypted text | |
| 838 */ | |
| 839 public function encrypt($clear, $key = 'des_key', $base64 = true) | |
| 840 { | |
| 841 if (!is_string($clear) || !strlen($clear)) { | |
| 842 return ''; | |
| 843 } | |
| 844 | |
| 845 $ckey = $this->config->get_crypto_key($key); | |
| 846 $method = $this->config->get_crypto_method(); | |
| 847 $opts = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true; | |
| 848 $iv = rcube_utils::random_bytes(openssl_cipher_iv_length($method), true); | |
| 849 $cipher = $iv . openssl_encrypt($clear, $method, $ckey, $opts, $iv); | |
| 850 | |
| 851 return $base64 ? base64_encode($cipher) : $cipher; | |
| 852 } | |
| 853 | |
| 854 /** | |
| 855 * Decrypt a string | |
| 856 * | |
| 857 * @param string $cipher Encrypted text | |
| 858 * @param string $key Encryption key to retrieve from the configuration, defaults to 'des_key' | |
| 859 * @param boolean $base64 Whether or not input is base64-encoded | |
| 860 * | |
| 861 * @return string Decrypted text | |
| 862 */ | |
| 863 public function decrypt($cipher, $key = 'des_key', $base64 = true) | |
| 864 { | |
| 865 if (!$cipher) { | |
| 866 return ''; | |
| 867 } | |
| 868 | |
| 869 $cipher = $base64 ? base64_decode($cipher) : $cipher; | |
| 870 $ckey = $this->config->get_crypto_key($key); | |
| 871 $method = $this->config->get_crypto_method(); | |
| 872 $opts = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true; | |
| 873 $iv_size = openssl_cipher_iv_length($method); | |
| 874 $iv = substr($cipher, 0, $iv_size); | |
| 875 | |
| 876 // session corruption? (#1485970) | |
| 877 if (strlen($iv) < $iv_size) { | |
| 878 return ''; | |
| 879 } | |
| 880 | |
| 881 $cipher = substr($cipher, $iv_size); | |
| 882 $clear = openssl_decrypt($cipher, $method, $ckey, $opts, $iv); | |
| 883 | |
| 884 return $clear; | |
| 885 } | |
| 886 | |
| 887 /** | |
| 888 * Returns session token for secure URLs | |
| 889 * | |
| 890 * @param bool $generate Generate token if not exists in session yet | |
| 891 * | |
| 892 * @return string|bool Token string, False when disabled | |
| 893 */ | |
| 894 public function get_secure_url_token($generate = false) | |
| 895 { | |
| 896 if ($len = $this->config->get('use_secure_urls')) { | |
| 897 if (empty($_SESSION['secure_token']) && $generate) { | |
| 898 // generate x characters long token | |
| 899 $length = $len > 1 ? $len : 16; | |
| 900 $token = rcube_utils::random_bytes($length); | |
| 901 | |
| 902 $plugin = $this->plugins->exec_hook('secure_token', | |
| 903 array('value' => $token, 'length' => $length)); | |
| 904 | |
| 905 $_SESSION['secure_token'] = $plugin['value']; | |
| 906 } | |
| 907 | |
| 908 return $_SESSION['secure_token']; | |
| 909 } | |
| 910 | |
| 911 return false; | |
| 912 } | |
| 913 | |
| 914 /** | |
| 915 * Generate a unique token to be used in a form request | |
| 916 * | |
| 917 * @return string The request token | |
| 918 */ | |
| 919 public function get_request_token() | |
| 920 { | |
| 921 if (empty($_SESSION['request_token'])) { | |
| 922 $plugin = $this->plugins->exec_hook('request_token', array( | |
| 923 'value' => rcube_utils::random_bytes(32))); | |
| 924 | |
| 925 $_SESSION['request_token'] = $plugin['value']; | |
| 926 } | |
| 927 | |
| 928 return $_SESSION['request_token']; | |
| 929 } | |
| 930 | |
| 931 /** | |
| 932 * Check if the current request contains a valid token. | |
| 933 * Empty requests aren't checked until use_secure_urls is set. | |
| 934 * | |
| 935 * @param int $mode Request method | |
| 936 * | |
| 937 * @return boolean True if request token is valid false if not | |
| 938 */ | |
| 939 public function check_request($mode = rcube_utils::INPUT_POST) | |
| 940 { | |
| 941 // check secure token in URL if enabled | |
| 942 if ($token = $this->get_secure_url_token()) { | |
| 943 foreach (explode('/', preg_replace('/[?#&].*$/', '', $_SERVER['REQUEST_URI'])) as $tok) { | |
| 944 if ($tok == $token) { | |
| 945 return true; | |
| 946 } | |
| 947 } | |
| 948 | |
| 949 $this->request_status = self::REQUEST_ERROR_URL; | |
| 950 | |
| 951 return false; | |
| 952 } | |
| 953 | |
| 954 $sess_tok = $this->get_request_token(); | |
| 955 | |
| 956 // ajax requests | |
| 957 if (rcube_utils::request_header('X-Roundcube-Request') == $sess_tok) { | |
| 958 return true; | |
| 959 } | |
| 960 | |
| 961 // skip empty requests | |
| 962 if (($mode == rcube_utils::INPUT_POST && empty($_POST)) | |
| 963 || ($mode == rcube_utils::INPUT_GET && empty($_GET)) | |
| 964 ) { | |
| 965 return true; | |
| 966 } | |
| 967 | |
| 968 // default method of securing requests | |
| 969 $token = rcube_utils::get_input_value('_token', $mode); | |
| 970 $sess_id = $_COOKIE[ini_get('session.name')]; | |
| 971 | |
| 972 if (empty($sess_id) || $token != $sess_tok) { | |
| 973 $this->request_status = self::REQUEST_ERROR_TOKEN; | |
| 974 return false; | |
| 975 } | |
| 976 | |
| 977 return true; | |
| 978 } | |
| 979 | |
| 980 /** | |
| 981 * Build a valid URL to this instance of Roundcube | |
| 982 * | |
| 983 * @param mixed $p Either a string with the action or url parameters as key-value pairs | |
| 984 * | |
| 985 * @return string Valid application URL | |
| 986 */ | |
| 987 public function url($p) | |
| 988 { | |
| 989 // STUB: should be overloaded by the application | |
| 990 return ''; | |
| 991 } | |
| 992 | |
| 993 /** | |
| 994 * Function to be executed in script shutdown | |
| 995 * Registered with register_shutdown_function() | |
| 996 */ | |
| 997 public function shutdown() | |
| 998 { | |
| 999 foreach ($this->shutdown_functions as $function) { | |
| 1000 call_user_func($function); | |
| 1001 } | |
| 1002 | |
| 1003 // write session data as soon as possible and before | |
| 1004 // closing database connection, don't do this before | |
| 1005 // registered shutdown functions, they may need the session | |
| 1006 // Note: this will run registered gc handlers (ie. cache gc) | |
| 1007 if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) { | |
| 1008 $this->session->write_close(); | |
| 1009 } | |
| 1010 | |
| 1011 if (is_object($this->smtp)) { | |
| 1012 $this->smtp->disconnect(); | |
| 1013 } | |
| 1014 | |
| 1015 foreach ($this->caches as $cache) { | |
| 1016 if (is_object($cache)) { | |
| 1017 $cache->close(); | |
| 1018 } | |
| 1019 } | |
| 1020 | |
| 1021 if (is_object($this->storage)) { | |
| 1022 $this->storage->close(); | |
| 1023 } | |
| 1024 | |
| 1025 if ($this->config->get('log_driver') == 'syslog') { | |
| 1026 closelog(); | |
| 1027 } | |
| 1028 } | |
| 1029 | |
| 1030 /** | |
| 1031 * Registers shutdown function to be executed on shutdown. | |
| 1032 * The functions will be executed before destroying any | |
| 1033 * objects like smtp, imap, session, etc. | |
| 1034 * | |
| 1035 * @param callback $function Function callback | |
| 1036 */ | |
| 1037 public function add_shutdown_function($function) | |
| 1038 { | |
| 1039 $this->shutdown_functions[] = $function; | |
| 1040 } | |
| 1041 | |
| 1042 /** | |
| 1043 * When you're going to sleep the script execution for a longer time | |
| 1044 * it is good to close all external connections (sql, memcache, SMTP, IMAP). | |
| 1045 * | |
| 1046 * No action is required on wake up, all connections will be | |
| 1047 * re-established automatically. | |
| 1048 */ | |
| 1049 public function sleep() | |
| 1050 { | |
| 1051 foreach ($this->caches as $cache) { | |
| 1052 if (is_object($cache)) { | |
| 1053 $cache->close(); | |
| 1054 } | |
| 1055 } | |
| 1056 | |
| 1057 if ($this->storage) { | |
| 1058 $this->storage->close(); | |
| 1059 } | |
| 1060 | |
| 1061 if ($this->db) { | |
| 1062 $this->db->closeConnection(); | |
| 1063 } | |
| 1064 | |
| 1065 if ($this->memcache) { | |
| 1066 $this->memcache->close(); | |
| 1067 // after close() need to re-init memcache | |
| 1068 $this->memcache_init(); | |
| 1069 } | |
| 1070 | |
| 1071 if ($this->smtp) { | |
| 1072 $this->smtp->disconnect(); | |
| 1073 } | |
| 1074 } | |
| 1075 | |
| 1076 /** | |
| 1077 * Quote a given string. | |
| 1078 * Shortcut function for rcube_utils::rep_specialchars_output() | |
| 1079 * | |
| 1080 * @return string HTML-quoted string | |
| 1081 */ | |
| 1082 public static function Q($str, $mode = 'strict', $newlines = true) | |
| 1083 { | |
| 1084 return rcube_utils::rep_specialchars_output($str, 'html', $mode, $newlines); | |
| 1085 } | |
| 1086 | |
| 1087 /** | |
| 1088 * Quote a given string for javascript output. | |
| 1089 * Shortcut function for rcube_utils::rep_specialchars_output() | |
| 1090 * | |
| 1091 * @return string JS-quoted string | |
| 1092 */ | |
| 1093 public static function JQ($str) | |
| 1094 { | |
| 1095 return rcube_utils::rep_specialchars_output($str, 'js'); | |
| 1096 } | |
| 1097 | |
| 1098 /** | |
| 1099 * Construct shell command, execute it and return output as string. | |
| 1100 * Keywords {keyword} are replaced with arguments | |
| 1101 * | |
| 1102 * @param $cmd Format string with {keywords} to be replaced | |
| 1103 * @param $values (zero, one or more arrays can be passed) | |
| 1104 * | |
| 1105 * @return output of command. shell errors not detectable | |
| 1106 */ | |
| 1107 public static function exec(/* $cmd, $values1 = array(), ... */) | |
| 1108 { | |
| 1109 $args = func_get_args(); | |
| 1110 $cmd = array_shift($args); | |
| 1111 $values = $replacements = array(); | |
| 1112 | |
| 1113 // merge values into one array | |
| 1114 foreach ($args as $arg) { | |
| 1115 $values += (array)$arg; | |
| 1116 } | |
| 1117 | |
| 1118 preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER); | |
| 1119 foreach ($matches as $tags) { | |
| 1120 list(, $tag, $option, $key) = $tags; | |
| 1121 $parts = array(); | |
| 1122 | |
| 1123 if ($option) { | |
| 1124 foreach ((array)$values["-$key"] as $key => $value) { | |
| 1125 if ($value === true || $value === false || $value === null) { | |
| 1126 $parts[] = $value ? $key : ""; | |
| 1127 } | |
| 1128 else { | |
| 1129 foreach ((array)$value as $val) { | |
| 1130 $parts[] = "$key " . escapeshellarg($val); | |
| 1131 } | |
| 1132 } | |
| 1133 } | |
| 1134 } | |
| 1135 else { | |
| 1136 foreach ((array)$values[$key] as $value) { | |
| 1137 $parts[] = escapeshellarg($value); | |
| 1138 } | |
| 1139 } | |
| 1140 | |
| 1141 $replacements[$tag] = join(" ", $parts); | |
| 1142 } | |
| 1143 | |
| 1144 // use strtr behaviour of going through source string once | |
| 1145 $cmd = strtr($cmd, $replacements); | |
| 1146 | |
| 1147 return (string)shell_exec($cmd); | |
| 1148 } | |
| 1149 | |
| 1150 /** | |
| 1151 * Print or write debug messages | |
| 1152 * | |
| 1153 * @param mixed Debug message or data | |
| 1154 */ | |
| 1155 public static function console() | |
| 1156 { | |
| 1157 $args = func_get_args(); | |
| 1158 | |
| 1159 if (class_exists('rcube', false)) { | |
| 1160 $rcube = self::get_instance(); | |
| 1161 $plugin = $rcube->plugins->exec_hook('console', array('args' => $args)); | |
| 1162 if ($plugin['abort']) { | |
| 1163 return; | |
| 1164 } | |
| 1165 | |
| 1166 $args = $plugin['args']; | |
| 1167 } | |
| 1168 | |
| 1169 $msg = array(); | |
| 1170 foreach ($args as $arg) { | |
| 1171 $msg[] = !is_string($arg) ? var_export($arg, true) : $arg; | |
| 1172 } | |
| 1173 | |
| 1174 self::write_log('console', join(";\n", $msg)); | |
| 1175 } | |
| 1176 | |
| 1177 /** | |
| 1178 * Append a line to a logfile in the logs directory. | |
| 1179 * Date will be added automatically to the line. | |
| 1180 * | |
| 1181 * @param string $name Name of the log file | |
| 1182 * @param mixed $line Line to append | |
| 1183 * | |
| 1184 * @return bool True on success, False on failure | |
| 1185 */ | |
| 1186 public static function write_log($name, $line) | |
| 1187 { | |
| 1188 if (!is_string($line)) { | |
| 1189 $line = var_export($line, true); | |
| 1190 } | |
| 1191 | |
| 1192 $date_format = $log_driver = $session_key = null; | |
| 1193 if (self::$instance) { | |
| 1194 $date_format = self::$instance->config->get('log_date_format'); | |
| 1195 $log_driver = self::$instance->config->get('log_driver'); | |
| 1196 $session_key = intval(self::$instance->config->get('log_session_id', 8)); | |
| 1197 } | |
| 1198 | |
| 1199 $date = rcube_utils::date_format($date_format); | |
| 1200 | |
| 1201 // trigger logging hook | |
| 1202 if (is_object(self::$instance) && is_object(self::$instance->plugins)) { | |
| 1203 $log = self::$instance->plugins->exec_hook('write_log', | |
| 1204 array('name' => $name, 'date' => $date, 'line' => $line)); | |
| 1205 | |
| 1206 $name = $log['name']; | |
| 1207 $line = $log['line']; | |
| 1208 $date = $log['date']; | |
| 1209 | |
| 1210 if ($log['abort']) { | |
| 1211 return true; | |
| 1212 } | |
| 1213 } | |
| 1214 | |
| 1215 // add session ID to the log | |
| 1216 if ($session_key > 0 && ($sess = session_id())) { | |
| 1217 $line = '<' . substr($sess, 0, $session_key) . '> ' . $line; | |
| 1218 } | |
| 1219 | |
| 1220 if ($log_driver == 'syslog') { | |
| 1221 $prio = $name == 'errors' ? LOG_ERR : LOG_INFO; | |
| 1222 return syslog($prio, $line); | |
| 1223 } | |
| 1224 | |
| 1225 // write message with file name when configured to log to STDOUT | |
| 1226 if ($log_driver == 'stdout') { | |
| 1227 $stdout = "php://stdout"; | |
| 1228 $line = "$name: $line"; | |
| 1229 return file_put_contents($stdout, $line, FILE_APPEND) !== false; | |
| 1230 } | |
| 1231 | |
| 1232 // log_driver == 'file' is assumed here | |
| 1233 | |
| 1234 $line = sprintf("[%s]: %s\n", $date, $line); | |
| 1235 | |
| 1236 // per-user logging is activated | |
| 1237 if (self::$instance && self::$instance->config->get('per_user_logging') && self::$instance->get_user_id()) { | |
| 1238 $log_dir = self::$instance->get_user_log_dir(); | |
| 1239 if (empty($log_dir) && !in_array($name, array('errors', 'userlogins', 'sendmail'))) { | |
| 1240 return false; | |
| 1241 } | |
| 1242 } | |
| 1243 | |
| 1244 if (empty($log_dir)) { | |
| 1245 if (!empty($log['dir'])) { | |
| 1246 $log_dir = $log['dir']; | |
| 1247 } | |
| 1248 else if (self::$instance) { | |
| 1249 $log_dir = self::$instance->config->get('log_dir'); | |
| 1250 } | |
| 1251 } | |
| 1252 | |
| 1253 if (empty($log_dir)) { | |
| 1254 $log_dir = RCUBE_INSTALL_PATH . 'logs'; | |
| 1255 } | |
| 1256 | |
| 1257 return file_put_contents("$log_dir/$name", $line, FILE_APPEND) !== false; | |
| 1258 } | |
| 1259 | |
| 1260 /** | |
| 1261 * Throw system error (and show error page). | |
| 1262 * | |
| 1263 * @param array $arg Named parameters | |
| 1264 * - code: Error code (required) | |
| 1265 * - type: Error type [php|db|imap|javascript] | |
| 1266 * - message: Error message | |
| 1267 * - file: File where error occurred | |
| 1268 * - line: Line where error occurred | |
| 1269 * @param boolean $log True to log the error | |
| 1270 * @param boolean $terminate Terminate script execution | |
| 1271 */ | |
| 1272 public static function raise_error($arg = array(), $log = false, $terminate = false) | |
| 1273 { | |
| 1274 // handle PHP exceptions | |
| 1275 if (is_object($arg) && is_a($arg, 'Exception')) { | |
| 1276 $arg = array( | |
| 1277 'code' => $arg->getCode(), | |
| 1278 'line' => $arg->getLine(), | |
| 1279 'file' => $arg->getFile(), | |
| 1280 'message' => $arg->getMessage(), | |
| 1281 ); | |
| 1282 } | |
| 1283 else if (is_string($arg)) { | |
| 1284 $arg = array('message' => $arg); | |
| 1285 } | |
| 1286 | |
| 1287 if (empty($arg['code'])) { | |
| 1288 $arg['code'] = 500; | |
| 1289 } | |
| 1290 | |
| 1291 $cli = php_sapi_name() == 'cli'; | |
| 1292 | |
| 1293 // installer | |
| 1294 if (!$cli && class_exists('rcmail_install', false)) { | |
| 1295 $rci = rcmail_install::get_instance(); | |
| 1296 $rci->raise_error($arg); | |
| 1297 return; | |
| 1298 } | |
| 1299 | |
| 1300 if (($log || $terminate) && !$cli && $arg['message']) { | |
| 1301 $arg['fatal'] = $terminate; | |
| 1302 self::log_bug($arg); | |
| 1303 } | |
| 1304 | |
| 1305 // terminate script | |
| 1306 if ($terminate) { | |
| 1307 // display error page | |
| 1308 if (is_object(self::$instance->output)) { | |
| 1309 self::$instance->output->raise_error($arg['code'], $arg['message']); | |
| 1310 } | |
| 1311 else if ($cli) { | |
| 1312 fwrite(STDERR, 'ERROR: ' . $arg['message']); | |
| 1313 } | |
| 1314 | |
| 1315 exit(1); | |
| 1316 } | |
| 1317 else if ($cli) { | |
| 1318 fwrite(STDERR, 'ERROR: ' . $arg['message']); | |
| 1319 } | |
| 1320 } | |
| 1321 | |
| 1322 /** | |
| 1323 * Report error according to configured debug_level | |
| 1324 * | |
| 1325 * @param array $arg_arr Named parameters | |
| 1326 * @see self::raise_error() | |
| 1327 */ | |
| 1328 public static function log_bug($arg_arr) | |
| 1329 { | |
| 1330 $program = strtoupper($arg_arr['type'] ?: 'php'); | |
| 1331 $level = self::get_instance()->config->get('debug_level'); | |
| 1332 | |
| 1333 // disable errors for ajax requests, write to log instead (#1487831) | |
| 1334 if (($level & 4) && !empty($_REQUEST['_remote'])) { | |
| 1335 $level = ($level ^ 4) | 1; | |
| 1336 } | |
| 1337 | |
| 1338 // write error to local log file | |
| 1339 if (($level & 1) || !empty($arg_arr['fatal'])) { | |
| 1340 if ($_SERVER['REQUEST_METHOD'] == 'POST') { | |
| 1341 foreach (array('_task', '_action') as $arg) { | |
| 1342 if ($_POST[$arg] && !$_GET[$arg]) { | |
| 1343 $post_query[$arg] = $_POST[$arg]; | |
| 1344 } | |
| 1345 } | |
| 1346 | |
| 1347 if (!empty($post_query)) { | |
| 1348 $post_query = (strpos($_SERVER['REQUEST_URI'], '?') != false ? '&' : '?') | |
| 1349 . http_build_query($post_query, '', '&'); | |
| 1350 } | |
| 1351 } | |
| 1352 | |
| 1353 $log_entry = sprintf("%s Error: %s%s (%s %s)", | |
| 1354 $program, | |
| 1355 $arg_arr['message'], | |
| 1356 $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '', | |
| 1357 $_SERVER['REQUEST_METHOD'], | |
| 1358 $_SERVER['REQUEST_URI'] . $post_query); | |
| 1359 | |
| 1360 if (!self::write_log('errors', $log_entry)) { | |
| 1361 // send error to PHPs error handler if write_log didn't succeed | |
| 1362 trigger_error($arg_arr['message'], E_USER_WARNING); | |
| 1363 } | |
| 1364 } | |
| 1365 | |
| 1366 // report the bug to the global bug reporting system | |
| 1367 if ($level & 2) { | |
| 1368 // TODO: Send error via HTTP | |
| 1369 } | |
| 1370 | |
| 1371 // show error if debug_mode is on | |
| 1372 if ($level & 4) { | |
| 1373 print "<b>$program Error"; | |
| 1374 | |
| 1375 if (!empty($arg_arr['file']) && !empty($arg_arr['line'])) { | |
| 1376 print " in $arg_arr[file] ($arg_arr[line])"; | |
| 1377 } | |
| 1378 | |
| 1379 print ':</b> '; | |
| 1380 print nl2br($arg_arr['message']); | |
| 1381 print '<br />'; | |
| 1382 flush(); | |
| 1383 } | |
| 1384 } | |
| 1385 | |
| 1386 /** | |
| 1387 * Write debug info to the log | |
| 1388 * | |
| 1389 * @param string $engine Engine type - file name (memcache, apc) | |
| 1390 * @param string $data Data string to log | |
| 1391 * @param bool $result Operation result | |
| 1392 */ | |
| 1393 public static function debug($engine, $data, $result = null) | |
| 1394 { | |
| 1395 static $debug_counter; | |
| 1396 | |
| 1397 $line = '[' . (++$debug_counter[$engine]) . '] ' . $data; | |
| 1398 | |
| 1399 if (($len = strlen($line)) > self::DEBUG_LINE_LENGTH) { | |
| 1400 $diff = $len - self::DEBUG_LINE_LENGTH; | |
| 1401 $line = substr($line, 0, self::DEBUG_LINE_LENGTH) . "... [truncated $diff bytes]"; | |
| 1402 } | |
| 1403 | |
| 1404 if ($result !== null) { | |
| 1405 $line .= ' [' . ($result ? 'TRUE' : 'FALSE') . ']'; | |
| 1406 } | |
| 1407 | |
| 1408 self::write_log($engine, $line); | |
| 1409 } | |
| 1410 | |
| 1411 /** | |
| 1412 * Returns current time (with microseconds). | |
| 1413 * | |
| 1414 * @return float Current time in seconds since the Unix | |
| 1415 */ | |
| 1416 public static function timer() | |
| 1417 { | |
| 1418 return microtime(true); | |
| 1419 } | |
| 1420 | |
| 1421 /** | |
| 1422 * Logs time difference according to provided timer | |
| 1423 * | |
| 1424 * @param float $timer Timer (self::timer() result) | |
| 1425 * @param string $label Log line prefix | |
| 1426 * @param string $dest Log file name | |
| 1427 * | |
| 1428 * @see self::timer() | |
| 1429 */ | |
| 1430 public static function print_timer($timer, $label = 'Timer', $dest = 'console') | |
| 1431 { | |
| 1432 static $print_count = 0; | |
| 1433 | |
| 1434 $print_count++; | |
| 1435 $now = self::timer(); | |
| 1436 $diff = $now - $timer; | |
| 1437 | |
| 1438 if (empty($label)) { | |
| 1439 $label = 'Timer '.$print_count; | |
| 1440 } | |
| 1441 | |
| 1442 self::write_log($dest, sprintf("%s: %0.4f sec", $label, $diff)); | |
| 1443 } | |
| 1444 | |
| 1445 /** | |
| 1446 * Setter for system user object | |
| 1447 * | |
| 1448 * @param rcube_user Current user instance | |
| 1449 */ | |
| 1450 public function set_user($user) | |
| 1451 { | |
| 1452 if (is_object($user)) { | |
| 1453 $this->user = $user; | |
| 1454 | |
| 1455 // overwrite config with user preferences | |
| 1456 $this->config->set_user_prefs((array)$this->user->get_prefs()); | |
| 1457 } | |
| 1458 } | |
| 1459 | |
| 1460 /** | |
| 1461 * Getter for logged user ID. | |
| 1462 * | |
| 1463 * @return mixed User identifier | |
| 1464 */ | |
| 1465 public function get_user_id() | |
| 1466 { | |
| 1467 if (is_object($this->user)) { | |
| 1468 return $this->user->ID; | |
| 1469 } | |
| 1470 else if (isset($_SESSION['user_id'])) { | |
| 1471 return $_SESSION['user_id']; | |
| 1472 } | |
| 1473 | |
| 1474 return null; | |
| 1475 } | |
| 1476 | |
| 1477 /** | |
| 1478 * Getter for logged user name. | |
| 1479 * | |
| 1480 * @return string User name | |
| 1481 */ | |
| 1482 public function get_user_name() | |
| 1483 { | |
| 1484 if (is_object($this->user)) { | |
| 1485 return $this->user->get_username(); | |
| 1486 } | |
| 1487 else if (isset($_SESSION['username'])) { | |
| 1488 return $_SESSION['username']; | |
| 1489 } | |
| 1490 } | |
| 1491 | |
| 1492 /** | |
| 1493 * Getter for logged user email (derived from user name not identity). | |
| 1494 * | |
| 1495 * @return string User email address | |
| 1496 */ | |
| 1497 public function get_user_email() | |
| 1498 { | |
| 1499 if (is_object($this->user)) { | |
| 1500 return $this->user->get_username('mail'); | |
| 1501 } | |
| 1502 } | |
| 1503 | |
| 1504 /** | |
| 1505 * Getter for logged user password. | |
| 1506 * | |
| 1507 * @return string User password | |
| 1508 */ | |
| 1509 public function get_user_password() | |
| 1510 { | |
| 1511 if ($this->password) { | |
| 1512 return $this->password; | |
| 1513 } | |
| 1514 else if ($_SESSION['password']) { | |
| 1515 return $this->decrypt($_SESSION['password']); | |
| 1516 } | |
| 1517 } | |
| 1518 | |
| 1519 /** | |
| 1520 * Get the per-user log directory | |
| 1521 */ | |
| 1522 protected function get_user_log_dir() | |
| 1523 { | |
| 1524 $log_dir = $this->config->get('log_dir', RCUBE_INSTALL_PATH . 'logs'); | |
| 1525 $user_name = $this->get_user_name(); | |
| 1526 $user_log_dir = $log_dir . '/' . $user_name; | |
| 1527 | |
| 1528 return !empty($user_name) && is_writable($user_log_dir) ? $user_log_dir : false; | |
| 1529 } | |
| 1530 | |
| 1531 /** | |
| 1532 * Getter for logged user language code. | |
| 1533 * | |
| 1534 * @return string User language code | |
| 1535 */ | |
| 1536 public function get_user_language() | |
| 1537 { | |
| 1538 if (is_object($this->user)) { | |
| 1539 return $this->user->language; | |
| 1540 } | |
| 1541 else if (isset($_SESSION['language'])) { | |
| 1542 return $_SESSION['language']; | |
| 1543 } | |
| 1544 } | |
| 1545 | |
| 1546 /** | |
| 1547 * Unique Message-ID generator. | |
| 1548 * | |
| 1549 * @param string $sender Optional sender e-mail address | |
| 1550 * | |
| 1551 * @return string Message-ID | |
| 1552 */ | |
| 1553 public function gen_message_id($sender = null) | |
| 1554 { | |
| 1555 $local_part = md5(uniqid('rcube'.mt_rand(), true)); | |
| 1556 $domain_part = ''; | |
| 1557 | |
| 1558 if ($sender && preg_match('/@([^\s]+\.[a-z0-9-]+)/', $sender, $m)) { | |
| 1559 $domain_part = $m[1]; | |
| 1560 } | |
| 1561 else { | |
| 1562 $domain_part = $this->user->get_username('domain'); | |
| 1563 } | |
| 1564 | |
| 1565 // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924) | |
| 1566 if (!preg_match('/\.[a-z0-9-]+$/i', $domain_part)) { | |
| 1567 foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) { | |
| 1568 $host = preg_replace('/:[0-9]+$/', '', $host); | |
| 1569 if ($host && preg_match('/\.[a-z]+$/i', $host)) { | |
| 1570 $domain_part = $host; | |
| 1571 break; | |
| 1572 } | |
| 1573 } | |
| 1574 } | |
| 1575 | |
| 1576 return sprintf('<%s@%s>', $local_part, $domain_part); | |
| 1577 } | |
| 1578 | |
| 1579 /** | |
| 1580 * Send the given message using the configured method. | |
| 1581 * | |
| 1582 * @param object $message Reference to Mail_MIME object | |
| 1583 * @param string $from Sender address string | |
| 1584 * @param array $mailto Array of recipient address strings | |
| 1585 * @param array $error SMTP error array (reference) | |
| 1586 * @param string $body_file Location of file with saved message body (reference), | |
| 1587 * used when delay_file_io is enabled | |
| 1588 * @param array $options SMTP options (e.g. DSN request) | |
| 1589 * @param bool $disconnect Close SMTP connection ASAP | |
| 1590 * | |
| 1591 * @return boolean Send status. | |
| 1592 */ | |
| 1593 public function deliver_message(&$message, $from, $mailto, &$error, | |
| 1594 &$body_file = null, $options = null, $disconnect = false) | |
| 1595 { | |
| 1596 $plugin = $this->plugins->exec_hook('message_before_send', array( | |
| 1597 'message' => $message, | |
| 1598 'from' => $from, | |
| 1599 'mailto' => $mailto, | |
| 1600 'options' => $options, | |
| 1601 )); | |
| 1602 | |
| 1603 if ($plugin['abort']) { | |
| 1604 if (!empty($plugin['error'])) { | |
| 1605 $error = $plugin['error']; | |
| 1606 } | |
| 1607 if (!empty($plugin['body_file'])) { | |
| 1608 $body_file = $plugin['body_file']; | |
| 1609 } | |
| 1610 | |
| 1611 return isset($plugin['result']) ? $plugin['result'] : false; | |
| 1612 } | |
| 1613 | |
| 1614 $from = $plugin['from']; | |
| 1615 $mailto = $plugin['mailto']; | |
| 1616 $options = $plugin['options']; | |
| 1617 $message = $plugin['message']; | |
| 1618 $headers = $message->headers(); | |
| 1619 | |
| 1620 // generate list of recipients | |
| 1621 $a_recipients = (array) $mailto; | |
| 1622 | |
| 1623 if (strlen($headers['Cc'])) { | |
| 1624 $a_recipients[] = $headers['Cc']; | |
| 1625 } | |
| 1626 if (strlen($headers['Bcc'])) { | |
| 1627 $a_recipients[] = $headers['Bcc']; | |
| 1628 } | |
| 1629 | |
| 1630 // remove Bcc header and get the whole head of the message as string | |
| 1631 $smtp_headers = $message->txtHeaders(array('Bcc' => null), true); | |
| 1632 | |
| 1633 if ($message->getParam('delay_file_io')) { | |
| 1634 // use common temp dir | |
| 1635 $temp_dir = $this->config->get('temp_dir'); | |
| 1636 $body_file = tempnam($temp_dir, 'rcmMsg'); | |
| 1637 $mime_result = $message->saveMessageBody($body_file); | |
| 1638 | |
| 1639 if (is_a($mime_result, 'PEAR_Error')) { | |
| 1640 self::raise_error(array('code' => 650, 'type' => 'php', | |
| 1641 'file' => __FILE__, 'line' => __LINE__, | |
| 1642 'message' => "Could not create message: ".$mime_result->getMessage()), | |
| 1643 true, false); | |
| 1644 return false; | |
| 1645 } | |
| 1646 | |
| 1647 $msg_body = fopen($body_file, 'r'); | |
| 1648 } | |
| 1649 else { | |
| 1650 $msg_body = $message->get(); | |
| 1651 } | |
| 1652 | |
| 1653 // initialize SMTP connection | |
| 1654 if (!is_object($this->smtp)) { | |
| 1655 $this->smtp_init(true); | |
| 1656 } | |
| 1657 | |
| 1658 // send message | |
| 1659 $sent = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $options); | |
| 1660 $response = $this->smtp->get_response(); | |
| 1661 $error = $this->smtp->get_error(); | |
| 1662 | |
| 1663 if (!$sent) { | |
| 1664 self::raise_error(array('code' => 800, 'type' => 'smtp', | |
| 1665 'line' => __LINE__, 'file' => __FILE__, | |
| 1666 'message' => join("\n", $response)), true, false); | |
| 1667 | |
| 1668 // allow plugins to catch sending errors with the same parameters as in 'message_before_send' | |
| 1669 $this->plugins->exec_hook('message_send_error', $plugin + array('error' => $error)); | |
| 1670 } | |
| 1671 else { | |
| 1672 $this->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body, 'message' => $message)); | |
| 1673 | |
| 1674 // remove MDN headers after sending | |
| 1675 unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']); | |
| 1676 | |
| 1677 if ($this->config->get('smtp_log')) { | |
| 1678 // get all recipient addresses | |
| 1679 $mailto = implode(',', $a_recipients); | |
| 1680 $mailto = rcube_mime::decode_address_list($mailto, null, false, null, true); | |
| 1681 | |
| 1682 self::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s", | |
| 1683 $this->user->get_username(), | |
| 1684 rcube_utils::remote_addr(), | |
| 1685 implode(', ', $mailto), | |
| 1686 !empty($response) ? join('; ', $response) : '')); | |
| 1687 } | |
| 1688 } | |
| 1689 | |
| 1690 if (is_resource($msg_body)) { | |
| 1691 fclose($msg_body); | |
| 1692 } | |
| 1693 | |
| 1694 if ($disconnect) { | |
| 1695 $this->smtp->disconnect(); | |
| 1696 } | |
| 1697 | |
| 1698 $message->headers($headers, true); | |
| 1699 | |
| 1700 return $sent; | |
| 1701 } | |
| 1702 } | |
| 1703 | |
| 1704 | |
| 1705 /** | |
| 1706 * Lightweight plugin API class serving as a dummy if plugins are not enabled | |
| 1707 * | |
| 1708 * @package Framework | |
| 1709 * @subpackage Core | |
| 1710 */ | |
| 1711 class rcube_dummy_plugin_api | |
| 1712 { | |
| 1713 /** | |
| 1714 * Triggers a plugin hook. | |
| 1715 * @see rcube_plugin_api::exec_hook() | |
| 1716 */ | |
| 1717 public function exec_hook($hook, $args = array()) | |
| 1718 { | |
| 1719 return $args; | |
| 1720 } | |
| 1721 } |
