Mercurial > hg > rc2
comparison program/lib/Roundcube/rcube.php @ 0:4681f974d28b
vanilla 1.3.3 distro, I hope
author | Charlie Root |
---|---|
date | Thu, 04 Jan 2018 15:52:31 -0500 |
parents | |
children | 3a5f959af5ae |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4681f974d28b |
---|---|
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 | |
624 // check for text with domain | |
625 if ($domain && ($text = $this->texts[$domain.'.'.$name])) { | |
626 } | |
627 // text does not exist | |
628 else if (!($text = $this->texts[$name])) { | |
629 return "[$name]"; | |
630 } | |
631 | |
632 // replace vars in text | |
633 if (is_array($attrib['vars'])) { | |
634 foreach ($attrib['vars'] as $var_key => $var_value) { | |
635 $text = str_replace($var_key[0] != '$' ? '$'.$var_key : $var_key, $var_value, $text); | |
636 } | |
637 } | |
638 | |
639 // replace \n with real line break | |
640 $text = strtr($text, array('\n' => "\n")); | |
641 | |
642 // case folding | |
643 if (($attrib['uppercase'] && strtolower($attrib['uppercase']) == 'first') || $attrib['ucfirst']) { | |
644 $case_mode = MB_CASE_TITLE; | |
645 } | |
646 else if ($attrib['uppercase']) { | |
647 $case_mode = MB_CASE_UPPER; | |
648 } | |
649 else if ($attrib['lowercase']) { | |
650 $case_mode = MB_CASE_LOWER; | |
651 } | |
652 | |
653 if (isset($case_mode)) { | |
654 $text = mb_convert_case($text, $case_mode); | |
655 } | |
656 | |
657 return $text; | |
658 } | |
659 | |
660 /** | |
661 * Check if the given text label exists | |
662 * | |
663 * @param string $name Label name | |
664 * @param string $domain Label domain (plugin) name or '*' for all domains | |
665 * @param string $ref_domain Sets domain name if label is found | |
666 * | |
667 * @return boolean True if text exists (either in the current language or in en_US) | |
668 */ | |
669 public function text_exists($name, $domain = null, &$ref_domain = null) | |
670 { | |
671 // load localization files if not done yet | |
672 if (empty($this->texts)) { | |
673 $this->load_language(); | |
674 } | |
675 | |
676 if (isset($this->texts[$name])) { | |
677 $ref_domain = ''; | |
678 return true; | |
679 } | |
680 | |
681 // any of loaded domains (plugins) | |
682 if ($domain == '*') { | |
683 foreach ($this->plugins->loaded_plugins() as $domain) { | |
684 if (isset($this->texts[$domain.'.'.$name])) { | |
685 $ref_domain = $domain; | |
686 return true; | |
687 } | |
688 } | |
689 } | |
690 // specified domain | |
691 else if ($domain) { | |
692 $ref_domain = $domain; | |
693 return isset($this->texts[$domain.'.'.$name]); | |
694 } | |
695 | |
696 return false; | |
697 } | |
698 | |
699 /** | |
700 * Load a localization package | |
701 * | |
702 * @param string $lang Language ID | |
703 * @param array $add Additional text labels/messages | |
704 * @param array $merge Additional text labels/messages to merge | |
705 */ | |
706 public function load_language($lang = null, $add = array(), $merge = array()) | |
707 { | |
708 $lang = $this->language_prop($lang ?: $_SESSION['language']); | |
709 | |
710 // load localized texts | |
711 if (empty($this->texts) || $lang != $_SESSION['language']) { | |
712 $this->texts = array(); | |
713 | |
714 // handle empty lines after closing PHP tag in localization files | |
715 ob_start(); | |
716 | |
717 // get english labels (these should be complete) | |
718 @include(RCUBE_LOCALIZATION_DIR . 'en_US/labels.inc'); | |
719 @include(RCUBE_LOCALIZATION_DIR . 'en_US/messages.inc'); | |
720 | |
721 if (is_array($labels)) | |
722 $this->texts = $labels; | |
723 if (is_array($messages)) | |
724 $this->texts = array_merge($this->texts, $messages); | |
725 | |
726 // include user language files | |
727 if ($lang != 'en' && $lang != 'en_US' && is_dir(RCUBE_LOCALIZATION_DIR . $lang)) { | |
728 include_once(RCUBE_LOCALIZATION_DIR . $lang . '/labels.inc'); | |
729 include_once(RCUBE_LOCALIZATION_DIR . $lang . '/messages.inc'); | |
730 | |
731 if (is_array($labels)) | |
732 $this->texts = array_merge($this->texts, $labels); | |
733 if (is_array($messages)) | |
734 $this->texts = array_merge($this->texts, $messages); | |
735 } | |
736 | |
737 ob_end_clean(); | |
738 | |
739 $_SESSION['language'] = $lang; | |
740 } | |
741 | |
742 // append additional texts (from plugin) | |
743 if (is_array($add) && !empty($add)) { | |
744 $this->texts += $add; | |
745 } | |
746 | |
747 // merge additional texts (from plugin) | |
748 if (is_array($merge) && !empty($merge)) { | |
749 $this->texts = array_merge($this->texts, $merge); | |
750 } | |
751 } | |
752 | |
753 /** | |
754 * Check the given string and return a valid language code | |
755 * | |
756 * @param string $lang Language code | |
757 * | |
758 * @return string Valid language code | |
759 */ | |
760 protected function language_prop($lang) | |
761 { | |
762 static $rcube_languages, $rcube_language_aliases; | |
763 | |
764 // user HTTP_ACCEPT_LANGUAGE if no language is specified | |
765 if (empty($lang) || $lang == 'auto') { | |
766 $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); | |
767 $lang = $accept_langs[0]; | |
768 | |
769 if (preg_match('/^([a-z]+)[_-]([a-z]+)$/i', $lang, $m)) { | |
770 $lang = $m[1] . '_' . strtoupper($m[2]); | |
771 } | |
772 } | |
773 | |
774 if (empty($rcube_languages)) { | |
775 @include(RCUBE_LOCALIZATION_DIR . 'index.inc'); | |
776 } | |
777 | |
778 // check if we have an alias for that language | |
779 if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) { | |
780 $lang = $rcube_language_aliases[$lang]; | |
781 } | |
782 // try the first two chars | |
783 else if (!isset($rcube_languages[$lang])) { | |
784 $short = substr($lang, 0, 2); | |
785 | |
786 // check if we have an alias for the short language code | |
787 if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) { | |
788 $lang = $rcube_language_aliases[$short]; | |
789 } | |
790 // expand 'nn' to 'nn_NN' | |
791 else if (!isset($rcube_languages[$short])) { | |
792 $lang = $short.'_'.strtoupper($short); | |
793 } | |
794 } | |
795 | |
796 if (!isset($rcube_languages[$lang]) || !is_dir(RCUBE_LOCALIZATION_DIR . $lang)) { | |
797 $lang = 'en_US'; | |
798 } | |
799 | |
800 return $lang; | |
801 } | |
802 | |
803 /** | |
804 * Read directory program/localization and return a list of available languages | |
805 * | |
806 * @return array List of available localizations | |
807 */ | |
808 public function list_languages() | |
809 { | |
810 static $sa_languages = array(); | |
811 | |
812 if (!count($sa_languages)) { | |
813 @include(RCUBE_LOCALIZATION_DIR . 'index.inc'); | |
814 | |
815 if ($dh = @opendir(RCUBE_LOCALIZATION_DIR)) { | |
816 while (($name = readdir($dh)) !== false) { | |
817 if ($name[0] == '.' || !is_dir(RCUBE_LOCALIZATION_DIR . $name)) { | |
818 continue; | |
819 } | |
820 | |
821 if ($label = $rcube_languages[$name]) { | |
822 $sa_languages[$name] = $label; | |
823 } | |
824 } | |
825 closedir($dh); | |
826 } | |
827 } | |
828 | |
829 return $sa_languages; | |
830 } | |
831 | |
832 /** | |
833 * Encrypt a string | |
834 * | |
835 * @param string $clear Clear text input | |
836 * @param string $key Encryption key to retrieve from the configuration, defaults to 'des_key' | |
837 * @param boolean $base64 Whether or not to base64_encode() the result before returning | |
838 * | |
839 * @return string Encrypted text | |
840 */ | |
841 public function encrypt($clear, $key = 'des_key', $base64 = true) | |
842 { | |
843 if (!is_string($clear) || !strlen($clear)) { | |
844 return ''; | |
845 } | |
846 | |
847 $ckey = $this->config->get_crypto_key($key); | |
848 $method = $this->config->get_crypto_method(); | |
849 $opts = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true; | |
850 $iv = rcube_utils::random_bytes(openssl_cipher_iv_length($method), true); | |
851 $cipher = $iv . openssl_encrypt($clear, $method, $ckey, $opts, $iv); | |
852 | |
853 return $base64 ? base64_encode($cipher) : $cipher; | |
854 } | |
855 | |
856 /** | |
857 * Decrypt a string | |
858 * | |
859 * @param string $cipher Encrypted text | |
860 * @param string $key Encryption key to retrieve from the configuration, defaults to 'des_key' | |
861 * @param boolean $base64 Whether or not input is base64-encoded | |
862 * | |
863 * @return string Decrypted text | |
864 */ | |
865 public function decrypt($cipher, $key = 'des_key', $base64 = true) | |
866 { | |
867 if (!$cipher) { | |
868 return ''; | |
869 } | |
870 | |
871 $cipher = $base64 ? base64_decode($cipher) : $cipher; | |
872 $ckey = $this->config->get_crypto_key($key); | |
873 $method = $this->config->get_crypto_method(); | |
874 $opts = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true; | |
875 $iv_size = openssl_cipher_iv_length($method); | |
876 $iv = substr($cipher, 0, $iv_size); | |
877 | |
878 // session corruption? (#1485970) | |
879 if (strlen($iv) < $iv_size) { | |
880 return ''; | |
881 } | |
882 | |
883 $cipher = substr($cipher, $iv_size); | |
884 $clear = openssl_decrypt($cipher, $method, $ckey, $opts, $iv); | |
885 | |
886 return $clear; | |
887 } | |
888 | |
889 /** | |
890 * Returns session token for secure URLs | |
891 * | |
892 * @param bool $generate Generate token if not exists in session yet | |
893 * | |
894 * @return string|bool Token string, False when disabled | |
895 */ | |
896 public function get_secure_url_token($generate = false) | |
897 { | |
898 if ($len = $this->config->get('use_secure_urls')) { | |
899 if (empty($_SESSION['secure_token']) && $generate) { | |
900 // generate x characters long token | |
901 $length = $len > 1 ? $len : 16; | |
902 $token = rcube_utils::random_bytes($length); | |
903 | |
904 $plugin = $this->plugins->exec_hook('secure_token', | |
905 array('value' => $token, 'length' => $length)); | |
906 | |
907 $_SESSION['secure_token'] = $plugin['value']; | |
908 } | |
909 | |
910 return $_SESSION['secure_token']; | |
911 } | |
912 | |
913 return false; | |
914 } | |
915 | |
916 /** | |
917 * Generate a unique token to be used in a form request | |
918 * | |
919 * @return string The request token | |
920 */ | |
921 public function get_request_token() | |
922 { | |
923 if (empty($_SESSION['request_token'])) { | |
924 $plugin = $this->plugins->exec_hook('request_token', array( | |
925 'value' => rcube_utils::random_bytes(32))); | |
926 | |
927 $_SESSION['request_token'] = $plugin['value']; | |
928 } | |
929 | |
930 return $_SESSION['request_token']; | |
931 } | |
932 | |
933 /** | |
934 * Check if the current request contains a valid token. | |
935 * Empty requests aren't checked until use_secure_urls is set. | |
936 * | |
937 * @param int $mode Request method | |
938 * | |
939 * @return boolean True if request token is valid false if not | |
940 */ | |
941 public function check_request($mode = rcube_utils::INPUT_POST) | |
942 { | |
943 // check secure token in URL if enabled | |
944 if ($token = $this->get_secure_url_token()) { | |
945 foreach (explode('/', preg_replace('/[?#&].*$/', '', $_SERVER['REQUEST_URI'])) as $tok) { | |
946 if ($tok == $token) { | |
947 return true; | |
948 } | |
949 } | |
950 | |
951 $this->request_status = self::REQUEST_ERROR_URL; | |
952 | |
953 return false; | |
954 } | |
955 | |
956 $sess_tok = $this->get_request_token(); | |
957 | |
958 // ajax requests | |
959 if (rcube_utils::request_header('X-Roundcube-Request') == $sess_tok) { | |
960 return true; | |
961 } | |
962 | |
963 // skip empty requests | |
964 if (($mode == rcube_utils::INPUT_POST && empty($_POST)) | |
965 || ($mode == rcube_utils::INPUT_GET && empty($_GET)) | |
966 ) { | |
967 return true; | |
968 } | |
969 | |
970 // default method of securing requests | |
971 $token = rcube_utils::get_input_value('_token', $mode); | |
972 $sess_id = $_COOKIE[ini_get('session.name')]; | |
973 | |
974 if (empty($sess_id) || $token != $sess_tok) { | |
975 $this->request_status = self::REQUEST_ERROR_TOKEN; | |
976 return false; | |
977 } | |
978 | |
979 return true; | |
980 } | |
981 | |
982 /** | |
983 * Build a valid URL to this instance of Roundcube | |
984 * | |
985 * @param mixed $p Either a string with the action or url parameters as key-value pairs | |
986 * | |
987 * @return string Valid application URL | |
988 */ | |
989 public function url($p) | |
990 { | |
991 // STUB: should be overloaded by the application | |
992 return ''; | |
993 } | |
994 | |
995 /** | |
996 * Function to be executed in script shutdown | |
997 * Registered with register_shutdown_function() | |
998 */ | |
999 public function shutdown() | |
1000 { | |
1001 foreach ($this->shutdown_functions as $function) { | |
1002 call_user_func($function); | |
1003 } | |
1004 | |
1005 // write session data as soon as possible and before | |
1006 // closing database connection, don't do this before | |
1007 // registered shutdown functions, they may need the session | |
1008 // Note: this will run registered gc handlers (ie. cache gc) | |
1009 if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) { | |
1010 $this->session->write_close(); | |
1011 } | |
1012 | |
1013 if (is_object($this->smtp)) { | |
1014 $this->smtp->disconnect(); | |
1015 } | |
1016 | |
1017 foreach ($this->caches as $cache) { | |
1018 if (is_object($cache)) { | |
1019 $cache->close(); | |
1020 } | |
1021 } | |
1022 | |
1023 if (is_object($this->storage)) { | |
1024 $this->storage->close(); | |
1025 } | |
1026 | |
1027 if ($this->config->get('log_driver') == 'syslog') { | |
1028 closelog(); | |
1029 } | |
1030 } | |
1031 | |
1032 /** | |
1033 * Registers shutdown function to be executed on shutdown. | |
1034 * The functions will be executed before destroying any | |
1035 * objects like smtp, imap, session, etc. | |
1036 * | |
1037 * @param callback $function Function callback | |
1038 */ | |
1039 public function add_shutdown_function($function) | |
1040 { | |
1041 $this->shutdown_functions[] = $function; | |
1042 } | |
1043 | |
1044 /** | |
1045 * When you're going to sleep the script execution for a longer time | |
1046 * it is good to close all external connections (sql, memcache, SMTP, IMAP). | |
1047 * | |
1048 * No action is required on wake up, all connections will be | |
1049 * re-established automatically. | |
1050 */ | |
1051 public function sleep() | |
1052 { | |
1053 foreach ($this->caches as $cache) { | |
1054 if (is_object($cache)) { | |
1055 $cache->close(); | |
1056 } | |
1057 } | |
1058 | |
1059 if ($this->storage) { | |
1060 $this->storage->close(); | |
1061 } | |
1062 | |
1063 if ($this->db) { | |
1064 $this->db->closeConnection(); | |
1065 } | |
1066 | |
1067 if ($this->memcache) { | |
1068 $this->memcache->close(); | |
1069 // after close() need to re-init memcache | |
1070 $this->memcache_init(); | |
1071 } | |
1072 | |
1073 if ($this->smtp) { | |
1074 $this->smtp->disconnect(); | |
1075 } | |
1076 } | |
1077 | |
1078 /** | |
1079 * Quote a given string. | |
1080 * Shortcut function for rcube_utils::rep_specialchars_output() | |
1081 * | |
1082 * @return string HTML-quoted string | |
1083 */ | |
1084 public static function Q($str, $mode = 'strict', $newlines = true) | |
1085 { | |
1086 return rcube_utils::rep_specialchars_output($str, 'html', $mode, $newlines); | |
1087 } | |
1088 | |
1089 /** | |
1090 * Quote a given string for javascript output. | |
1091 * Shortcut function for rcube_utils::rep_specialchars_output() | |
1092 * | |
1093 * @return string JS-quoted string | |
1094 */ | |
1095 public static function JQ($str) | |
1096 { | |
1097 return rcube_utils::rep_specialchars_output($str, 'js'); | |
1098 } | |
1099 | |
1100 /** | |
1101 * Construct shell command, execute it and return output as string. | |
1102 * Keywords {keyword} are replaced with arguments | |
1103 * | |
1104 * @param $cmd Format string with {keywords} to be replaced | |
1105 * @param $values (zero, one or more arrays can be passed) | |
1106 * | |
1107 * @return output of command. shell errors not detectable | |
1108 */ | |
1109 public static function exec(/* $cmd, $values1 = array(), ... */) | |
1110 { | |
1111 $args = func_get_args(); | |
1112 $cmd = array_shift($args); | |
1113 $values = $replacements = array(); | |
1114 | |
1115 // merge values into one array | |
1116 foreach ($args as $arg) { | |
1117 $values += (array)$arg; | |
1118 } | |
1119 | |
1120 preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER); | |
1121 foreach ($matches as $tags) { | |
1122 list(, $tag, $option, $key) = $tags; | |
1123 $parts = array(); | |
1124 | |
1125 if ($option) { | |
1126 foreach ((array)$values["-$key"] as $key => $value) { | |
1127 if ($value === true || $value === false || $value === null) { | |
1128 $parts[] = $value ? $key : ""; | |
1129 } | |
1130 else { | |
1131 foreach ((array)$value as $val) { | |
1132 $parts[] = "$key " . escapeshellarg($val); | |
1133 } | |
1134 } | |
1135 } | |
1136 } | |
1137 else { | |
1138 foreach ((array)$values[$key] as $value) { | |
1139 $parts[] = escapeshellarg($value); | |
1140 } | |
1141 } | |
1142 | |
1143 $replacements[$tag] = join(" ", $parts); | |
1144 } | |
1145 | |
1146 // use strtr behaviour of going through source string once | |
1147 $cmd = strtr($cmd, $replacements); | |
1148 | |
1149 return (string)shell_exec($cmd); | |
1150 } | |
1151 | |
1152 /** | |
1153 * Print or write debug messages | |
1154 * | |
1155 * @param mixed Debug message or data | |
1156 */ | |
1157 public static function console() | |
1158 { | |
1159 $args = func_get_args(); | |
1160 | |
1161 if (class_exists('rcube', false)) { | |
1162 $rcube = self::get_instance(); | |
1163 $plugin = $rcube->plugins->exec_hook('console', array('args' => $args)); | |
1164 if ($plugin['abort']) { | |
1165 return; | |
1166 } | |
1167 | |
1168 $args = $plugin['args']; | |
1169 } | |
1170 | |
1171 $msg = array(); | |
1172 foreach ($args as $arg) { | |
1173 $msg[] = !is_string($arg) ? var_export($arg, true) : $arg; | |
1174 } | |
1175 | |
1176 self::write_log('console', join(";\n", $msg)); | |
1177 } | |
1178 | |
1179 /** | |
1180 * Append a line to a logfile in the logs directory. | |
1181 * Date will be added automatically to the line. | |
1182 * | |
1183 * @param string $name Name of the log file | |
1184 * @param mixed $line Line to append | |
1185 * | |
1186 * @return bool True on success, False on failure | |
1187 */ | |
1188 public static function write_log($name, $line) | |
1189 { | |
1190 if (!is_string($line)) { | |
1191 $line = var_export($line, true); | |
1192 } | |
1193 | |
1194 $date_format = $log_driver = $session_key = null; | |
1195 if (self::$instance) { | |
1196 $date_format = self::$instance->config->get('log_date_format'); | |
1197 $log_driver = self::$instance->config->get('log_driver'); | |
1198 $session_key = intval(self::$instance->config->get('log_session_id', 8)); | |
1199 } | |
1200 | |
1201 $date = rcube_utils::date_format($date_format); | |
1202 | |
1203 // trigger logging hook | |
1204 if (is_object(self::$instance) && is_object(self::$instance->plugins)) { | |
1205 $log = self::$instance->plugins->exec_hook('write_log', | |
1206 array('name' => $name, 'date' => $date, 'line' => $line)); | |
1207 | |
1208 $name = $log['name']; | |
1209 $line = $log['line']; | |
1210 $date = $log['date']; | |
1211 | |
1212 if ($log['abort']) { | |
1213 return true; | |
1214 } | |
1215 } | |
1216 | |
1217 // add session ID to the log | |
1218 if ($session_key > 0 && ($sess = session_id())) { | |
1219 $line = '<' . substr($sess, 0, $session_key) . '> ' . $line; | |
1220 } | |
1221 | |
1222 if ($log_driver == 'syslog') { | |
1223 $prio = $name == 'errors' ? LOG_ERR : LOG_INFO; | |
1224 return syslog($prio, $line); | |
1225 } | |
1226 | |
1227 // write message with file name when configured to log to STDOUT | |
1228 if ($log_driver == 'stdout') { | |
1229 $stdout = "php://stdout"; | |
1230 $line = "$name: $line"; | |
1231 return file_put_contents($stdout, $line, FILE_APPEND) !== false; | |
1232 } | |
1233 | |
1234 // log_driver == 'file' is assumed here | |
1235 | |
1236 $line = sprintf("[%s]: %s\n", $date, $line); | |
1237 | |
1238 // per-user logging is activated | |
1239 if (self::$instance && self::$instance->config->get('per_user_logging') && self::$instance->get_user_id()) { | |
1240 $log_dir = self::$instance->get_user_log_dir(); | |
1241 if (empty($log_dir) && !in_array($name, array('errors', 'userlogins', 'sendmail'))) { | |
1242 return false; | |
1243 } | |
1244 } | |
1245 | |
1246 if (empty($log_dir)) { | |
1247 if (!empty($log['dir'])) { | |
1248 $log_dir = $log['dir']; | |
1249 } | |
1250 else if (self::$instance) { | |
1251 $log_dir = self::$instance->config->get('log_dir'); | |
1252 } | |
1253 } | |
1254 | |
1255 if (empty($log_dir)) { | |
1256 $log_dir = RCUBE_INSTALL_PATH . 'logs'; | |
1257 } | |
1258 | |
1259 return file_put_contents("$log_dir/$name", $line, FILE_APPEND) !== false; | |
1260 } | |
1261 | |
1262 /** | |
1263 * Throw system error (and show error page). | |
1264 * | |
1265 * @param array $arg Named parameters | |
1266 * - code: Error code (required) | |
1267 * - type: Error type [php|db|imap|javascript] | |
1268 * - message: Error message | |
1269 * - file: File where error occurred | |
1270 * - line: Line where error occurred | |
1271 * @param boolean $log True to log the error | |
1272 * @param boolean $terminate Terminate script execution | |
1273 */ | |
1274 public static function raise_error($arg = array(), $log = false, $terminate = false) | |
1275 { | |
1276 // handle PHP exceptions | |
1277 if (is_object($arg) && is_a($arg, 'Exception')) { | |
1278 $arg = array( | |
1279 'code' => $arg->getCode(), | |
1280 'line' => $arg->getLine(), | |
1281 'file' => $arg->getFile(), | |
1282 'message' => $arg->getMessage(), | |
1283 ); | |
1284 } | |
1285 else if (is_string($arg)) { | |
1286 $arg = array('message' => $arg); | |
1287 } | |
1288 | |
1289 if (empty($arg['code'])) { | |
1290 $arg['code'] = 500; | |
1291 } | |
1292 | |
1293 $cli = php_sapi_name() == 'cli'; | |
1294 | |
1295 // installer | |
1296 if (!$cli && class_exists('rcmail_install', false)) { | |
1297 $rci = rcmail_install::get_instance(); | |
1298 $rci->raise_error($arg); | |
1299 return; | |
1300 } | |
1301 | |
1302 if (($log || $terminate) && !$cli && $arg['message']) { | |
1303 $arg['fatal'] = $terminate; | |
1304 self::log_bug($arg); | |
1305 } | |
1306 | |
1307 // terminate script | |
1308 if ($terminate) { | |
1309 // display error page | |
1310 if (is_object(self::$instance->output)) { | |
1311 self::$instance->output->raise_error($arg['code'], $arg['message']); | |
1312 } | |
1313 else if ($cli) { | |
1314 fwrite(STDERR, 'ERROR: ' . $arg['message']); | |
1315 } | |
1316 | |
1317 exit(1); | |
1318 } | |
1319 else if ($cli) { | |
1320 fwrite(STDERR, 'ERROR: ' . $arg['message']); | |
1321 } | |
1322 } | |
1323 | |
1324 /** | |
1325 * Report error according to configured debug_level | |
1326 * | |
1327 * @param array $arg_arr Named parameters | |
1328 * @see self::raise_error() | |
1329 */ | |
1330 public static function log_bug($arg_arr) | |
1331 { | |
1332 $program = strtoupper($arg_arr['type'] ?: 'php'); | |
1333 $level = self::get_instance()->config->get('debug_level'); | |
1334 | |
1335 // disable errors for ajax requests, write to log instead (#1487831) | |
1336 if (($level & 4) && !empty($_REQUEST['_remote'])) { | |
1337 $level = ($level ^ 4) | 1; | |
1338 } | |
1339 | |
1340 // write error to local log file | |
1341 if (($level & 1) || !empty($arg_arr['fatal'])) { | |
1342 if ($_SERVER['REQUEST_METHOD'] == 'POST') { | |
1343 foreach (array('_task', '_action') as $arg) { | |
1344 if ($_POST[$arg] && !$_GET[$arg]) { | |
1345 $post_query[$arg] = $_POST[$arg]; | |
1346 } | |
1347 } | |
1348 | |
1349 if (!empty($post_query)) { | |
1350 $post_query = (strpos($_SERVER['REQUEST_URI'], '?') != false ? '&' : '?') | |
1351 . http_build_query($post_query, '', '&'); | |
1352 } | |
1353 } | |
1354 | |
1355 $log_entry = sprintf("%s Error: %s%s (%s %s)", | |
1356 $program, | |
1357 $arg_arr['message'], | |
1358 $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '', | |
1359 $_SERVER['REQUEST_METHOD'], | |
1360 $_SERVER['REQUEST_URI'] . $post_query); | |
1361 | |
1362 if (!self::write_log('errors', $log_entry)) { | |
1363 // send error to PHPs error handler if write_log didn't succeed | |
1364 trigger_error($arg_arr['message'], E_USER_WARNING); | |
1365 } | |
1366 } | |
1367 | |
1368 // report the bug to the global bug reporting system | |
1369 if ($level & 2) { | |
1370 // TODO: Send error via HTTP | |
1371 } | |
1372 | |
1373 // show error if debug_mode is on | |
1374 if ($level & 4) { | |
1375 print "<b>$program Error"; | |
1376 | |
1377 if (!empty($arg_arr['file']) && !empty($arg_arr['line'])) { | |
1378 print " in $arg_arr[file] ($arg_arr[line])"; | |
1379 } | |
1380 | |
1381 print ':</b> '; | |
1382 print nl2br($arg_arr['message']); | |
1383 print '<br />'; | |
1384 flush(); | |
1385 } | |
1386 } | |
1387 | |
1388 /** | |
1389 * Write debug info to the log | |
1390 * | |
1391 * @param string $engine Engine type - file name (memcache, apc) | |
1392 * @param string $data Data string to log | |
1393 * @param bool $result Operation result | |
1394 */ | |
1395 public static function debug($engine, $data, $result = null) | |
1396 { | |
1397 static $debug_counter; | |
1398 | |
1399 $line = '[' . (++$debug_counter[$engine]) . '] ' . $data; | |
1400 | |
1401 if (($len = strlen($line)) > self::DEBUG_LINE_LENGTH) { | |
1402 $diff = $len - self::DEBUG_LINE_LENGTH; | |
1403 $line = substr($line, 0, self::DEBUG_LINE_LENGTH) . "... [truncated $diff bytes]"; | |
1404 } | |
1405 | |
1406 if ($result !== null) { | |
1407 $line .= ' [' . ($result ? 'TRUE' : 'FALSE') . ']'; | |
1408 } | |
1409 | |
1410 self::write_log($engine, $line); | |
1411 } | |
1412 | |
1413 /** | |
1414 * Returns current time (with microseconds). | |
1415 * | |
1416 * @return float Current time in seconds since the Unix | |
1417 */ | |
1418 public static function timer() | |
1419 { | |
1420 return microtime(true); | |
1421 } | |
1422 | |
1423 /** | |
1424 * Logs time difference according to provided timer | |
1425 * | |
1426 * @param float $timer Timer (self::timer() result) | |
1427 * @param string $label Log line prefix | |
1428 * @param string $dest Log file name | |
1429 * | |
1430 * @see self::timer() | |
1431 */ | |
1432 public static function print_timer($timer, $label = 'Timer', $dest = 'console') | |
1433 { | |
1434 static $print_count = 0; | |
1435 | |
1436 $print_count++; | |
1437 $now = self::timer(); | |
1438 $diff = $now - $timer; | |
1439 | |
1440 if (empty($label)) { | |
1441 $label = 'Timer '.$print_count; | |
1442 } | |
1443 | |
1444 self::write_log($dest, sprintf("%s: %0.4f sec", $label, $diff)); | |
1445 } | |
1446 | |
1447 /** | |
1448 * Setter for system user object | |
1449 * | |
1450 * @param rcube_user Current user instance | |
1451 */ | |
1452 public function set_user($user) | |
1453 { | |
1454 if (is_object($user)) { | |
1455 $this->user = $user; | |
1456 | |
1457 // overwrite config with user preferences | |
1458 $this->config->set_user_prefs((array)$this->user->get_prefs()); | |
1459 } | |
1460 } | |
1461 | |
1462 /** | |
1463 * Getter for logged user ID. | |
1464 * | |
1465 * @return mixed User identifier | |
1466 */ | |
1467 public function get_user_id() | |
1468 { | |
1469 if (is_object($this->user)) { | |
1470 return $this->user->ID; | |
1471 } | |
1472 else if (isset($_SESSION['user_id'])) { | |
1473 return $_SESSION['user_id']; | |
1474 } | |
1475 | |
1476 return null; | |
1477 } | |
1478 | |
1479 /** | |
1480 * Getter for logged user name. | |
1481 * | |
1482 * @return string User name | |
1483 */ | |
1484 public function get_user_name() | |
1485 { | |
1486 if (is_object($this->user)) { | |
1487 return $this->user->get_username(); | |
1488 } | |
1489 else if (isset($_SESSION['username'])) { | |
1490 return $_SESSION['username']; | |
1491 } | |
1492 } | |
1493 | |
1494 /** | |
1495 * Getter for logged user email (derived from user name not identity). | |
1496 * | |
1497 * @return string User email address | |
1498 */ | |
1499 public function get_user_email() | |
1500 { | |
1501 if (is_object($this->user)) { | |
1502 return $this->user->get_username('mail'); | |
1503 } | |
1504 } | |
1505 | |
1506 /** | |
1507 * Getter for logged user password. | |
1508 * | |
1509 * @return string User password | |
1510 */ | |
1511 public function get_user_password() | |
1512 { | |
1513 if ($this->password) { | |
1514 return $this->password; | |
1515 } | |
1516 else if ($_SESSION['password']) { | |
1517 return $this->decrypt($_SESSION['password']); | |
1518 } | |
1519 } | |
1520 | |
1521 /** | |
1522 * Get the per-user log directory | |
1523 */ | |
1524 protected function get_user_log_dir() | |
1525 { | |
1526 $log_dir = $this->config->get('log_dir', RCUBE_INSTALL_PATH . 'logs'); | |
1527 $user_name = $this->get_user_name(); | |
1528 $user_log_dir = $log_dir . '/' . $user_name; | |
1529 | |
1530 return !empty($user_name) && is_writable($user_log_dir) ? $user_log_dir : false; | |
1531 } | |
1532 | |
1533 /** | |
1534 * Getter for logged user language code. | |
1535 * | |
1536 * @return string User language code | |
1537 */ | |
1538 public function get_user_language() | |
1539 { | |
1540 if (is_object($this->user)) { | |
1541 return $this->user->language; | |
1542 } | |
1543 else if (isset($_SESSION['language'])) { | |
1544 return $_SESSION['language']; | |
1545 } | |
1546 } | |
1547 | |
1548 /** | |
1549 * Unique Message-ID generator. | |
1550 * | |
1551 * @param string $sender Optional sender e-mail address | |
1552 * | |
1553 * @return string Message-ID | |
1554 */ | |
1555 public function gen_message_id($sender = null) | |
1556 { | |
1557 $local_part = md5(uniqid('rcube'.mt_rand(), true)); | |
1558 $domain_part = ''; | |
1559 | |
1560 if ($sender && preg_match('/@([^\s]+\.[a-z0-9-]+)/', $sender, $m)) { | |
1561 $domain_part = $m[1]; | |
1562 } | |
1563 else { | |
1564 $domain_part = $this->user->get_username('domain'); | |
1565 } | |
1566 | |
1567 // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924) | |
1568 if (!preg_match('/\.[a-z0-9-]+$/i', $domain_part)) { | |
1569 foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) { | |
1570 $host = preg_replace('/:[0-9]+$/', '', $host); | |
1571 if ($host && preg_match('/\.[a-z]+$/i', $host)) { | |
1572 $domain_part = $host; | |
1573 break; | |
1574 } | |
1575 } | |
1576 } | |
1577 | |
1578 return sprintf('<%s@%s>', $local_part, $domain_part); | |
1579 } | |
1580 | |
1581 /** | |
1582 * Send the given message using the configured method. | |
1583 * | |
1584 * @param object $message Reference to Mail_MIME object | |
1585 * @param string $from Sender address string | |
1586 * @param array $mailto Array of recipient address strings | |
1587 * @param array $error SMTP error array (reference) | |
1588 * @param string $body_file Location of file with saved message body (reference), | |
1589 * used when delay_file_io is enabled | |
1590 * @param array $options SMTP options (e.g. DSN request) | |
1591 * @param bool $disconnect Close SMTP connection ASAP | |
1592 * | |
1593 * @return boolean Send status. | |
1594 */ | |
1595 public function deliver_message(&$message, $from, $mailto, &$error, | |
1596 &$body_file = null, $options = null, $disconnect = false) | |
1597 { | |
1598 $plugin = $this->plugins->exec_hook('message_before_send', array( | |
1599 'message' => $message, | |
1600 'from' => $from, | |
1601 'mailto' => $mailto, | |
1602 'options' => $options, | |
1603 )); | |
1604 | |
1605 if ($plugin['abort']) { | |
1606 if (!empty($plugin['error'])) { | |
1607 $error = $plugin['error']; | |
1608 } | |
1609 if (!empty($plugin['body_file'])) { | |
1610 $body_file = $plugin['body_file']; | |
1611 } | |
1612 | |
1613 return isset($plugin['result']) ? $plugin['result'] : false; | |
1614 } | |
1615 | |
1616 $from = $plugin['from']; | |
1617 $mailto = $plugin['mailto']; | |
1618 $options = $plugin['options']; | |
1619 $message = $plugin['message']; | |
1620 $headers = $message->headers(); | |
1621 | |
1622 // generate list of recipients | |
1623 $a_recipients = (array) $mailto; | |
1624 | |
1625 if (strlen($headers['Cc'])) { | |
1626 $a_recipients[] = $headers['Cc']; | |
1627 } | |
1628 if (strlen($headers['Bcc'])) { | |
1629 $a_recipients[] = $headers['Bcc']; | |
1630 } | |
1631 | |
1632 // remove Bcc header and get the whole head of the message as string | |
1633 $smtp_headers = $message->txtHeaders(array('Bcc' => null), true); | |
1634 | |
1635 if ($message->getParam('delay_file_io')) { | |
1636 // use common temp dir | |
1637 $temp_dir = $this->config->get('temp_dir'); | |
1638 $body_file = tempnam($temp_dir, 'rcmMsg'); | |
1639 $mime_result = $message->saveMessageBody($body_file); | |
1640 | |
1641 if (is_a($mime_result, 'PEAR_Error')) { | |
1642 self::raise_error(array('code' => 650, 'type' => 'php', | |
1643 'file' => __FILE__, 'line' => __LINE__, | |
1644 'message' => "Could not create message: ".$mime_result->getMessage()), | |
1645 true, false); | |
1646 return false; | |
1647 } | |
1648 | |
1649 $msg_body = fopen($body_file, 'r'); | |
1650 } | |
1651 else { | |
1652 $msg_body = $message->get(); | |
1653 } | |
1654 | |
1655 // initialize SMTP connection | |
1656 if (!is_object($this->smtp)) { | |
1657 $this->smtp_init(true); | |
1658 } | |
1659 | |
1660 // send message | |
1661 $sent = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $options); | |
1662 $response = $this->smtp->get_response(); | |
1663 $error = $this->smtp->get_error(); | |
1664 | |
1665 if (!$sent) { | |
1666 self::raise_error(array('code' => 800, 'type' => 'smtp', | |
1667 'line' => __LINE__, 'file' => __FILE__, | |
1668 'message' => join("\n", $response)), true, false); | |
1669 | |
1670 // allow plugins to catch sending errors with the same parameters as in 'message_before_send' | |
1671 $this->plugins->exec_hook('message_send_error', $plugin + array('error' => $error)); | |
1672 } | |
1673 else { | |
1674 $this->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body, 'message' => $message)); | |
1675 | |
1676 // remove MDN headers after sending | |
1677 unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']); | |
1678 | |
1679 if ($this->config->get('smtp_log')) { | |
1680 // get all recipient addresses | |
1681 $mailto = implode(',', $a_recipients); | |
1682 $mailto = rcube_mime::decode_address_list($mailto, null, false, null, true); | |
1683 | |
1684 self::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s", | |
1685 $this->user->get_username(), | |
1686 rcube_utils::remote_addr(), | |
1687 implode(', ', $mailto), | |
1688 !empty($response) ? join('; ', $response) : '')); | |
1689 } | |
1690 } | |
1691 | |
1692 if (is_resource($msg_body)) { | |
1693 fclose($msg_body); | |
1694 } | |
1695 | |
1696 if ($disconnect) { | |
1697 $this->smtp->disconnect(); | |
1698 } | |
1699 | |
1700 $message->headers($headers, true); | |
1701 | |
1702 return $sent; | |
1703 } | |
1704 } | |
1705 | |
1706 | |
1707 /** | |
1708 * Lightweight plugin API class serving as a dummy if plugins are not enabled | |
1709 * | |
1710 * @package Framework | |
1711 * @subpackage Core | |
1712 */ | |
1713 class rcube_dummy_plugin_api | |
1714 { | |
1715 /** | |
1716 * Triggers a plugin hook. | |
1717 * @see rcube_plugin_api::exec_hook() | |
1718 */ | |
1719 public function exec_hook($hook, $args = array()) | |
1720 { | |
1721 return $args; | |
1722 } | |
1723 } |