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