comparison program/lib/Roundcube/rcube_config.php @ 0:4681f974d28b

vanilla 1.3.3 distro, I hope
author Charlie Root
date Thu, 04 Jan 2018 15:52:31 -0500
parents
children
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 | |
8 | Licensed under the GNU General Public License version 3 or |
9 | any later version with exceptions for skins & plugins. |
10 | See the README file for a full license statement. |
11 | |
12 | PURPOSE: |
13 | Class to read configuration settings |
14 +-----------------------------------------------------------------------+
15 | Author: Thomas Bruederli <roundcube@gmail.com> |
16 +-----------------------------------------------------------------------+
17 */
18
19 /**
20 * Configuration class for Roundcube
21 *
22 * @package Framework
23 * @subpackage Core
24 */
25 class rcube_config
26 {
27 const DEFAULT_SKIN = 'larry';
28
29 private $env = '';
30 private $paths = array();
31 private $prop = array();
32 private $errors = array();
33 private $userprefs = array();
34
35
36 /**
37 * Renamed options
38 *
39 * @var array
40 */
41 private $legacy_props = array(
42 // new name => old name
43 'mail_pagesize' => 'pagesize',
44 'addressbook_pagesize' => 'pagesize',
45 'reply_mode' => 'top_posting',
46 'refresh_interval' => 'keep_alive',
47 'min_refresh_interval' => 'min_keep_alive',
48 'messages_cache_ttl' => 'message_cache_lifetime',
49 'mail_read_time' => 'preview_pane_mark_read',
50 'redundant_attachments_cache_ttl' => 'redundant_attachments_memcache_ttl',
51 );
52
53 /**
54 * Object constructor
55 *
56 * @param string $env Environment suffix for config files to load
57 */
58 public function __construct($env = '')
59 {
60 $this->env = $env;
61
62 if ($paths = getenv('RCUBE_CONFIG_PATH')) {
63 $this->paths = explode(PATH_SEPARATOR, $paths);
64 // make all paths absolute
65 foreach ($this->paths as $i => $path) {
66 if (!rcube_utils::is_absolute_path($path)) {
67 if ($realpath = realpath(RCUBE_INSTALL_PATH . $path)) {
68 $this->paths[$i] = unslashify($realpath) . '/';
69 }
70 else {
71 unset($this->paths[$i]);
72 }
73 }
74 else {
75 $this->paths[$i] = unslashify($path) . '/';
76 }
77 }
78 }
79
80 if (defined('RCUBE_CONFIG_DIR') && !in_array(RCUBE_CONFIG_DIR, $this->paths)) {
81 $this->paths[] = RCUBE_CONFIG_DIR;
82 }
83
84 if (empty($this->paths)) {
85 $this->paths[] = RCUBE_INSTALL_PATH . 'config/';
86 }
87
88 $this->load();
89
90 // Defaults, that we do not require you to configure,
91 // but contain information that is used in various locations in the code:
92 if (empty($this->prop['contactlist_fields'])) {
93 $this->set('contactlist_fields', array('name', 'firstname', 'surname', 'email'));
94 }
95 }
96
97 /**
98 * @brief Guess the type the string may fit into.
99 *
100 * Look inside the string to determine what type might be best as a container.
101 *
102 * @param mixed $value The value to inspect
103 *
104 * @return The guess at the type.
105 */
106 private function guess_type($value)
107 {
108 $type = 'string';
109
110 // array requires hint to be passed.
111
112 if (preg_match('/^[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?$/', $value) !== false) {
113 $type = 'double';
114 }
115 else if (preg_match('/^\d+$/', $value) !== false) {
116 $type = 'integer';
117 }
118 else if (preg_match('/(t(rue)?)|(f(alse)?)/i', $value) !== false) {
119 $type = 'boolean';
120 }
121
122 return $type;
123 }
124
125 /**
126 * @brief Parse environment variable into PHP type.
127 *
128 * Perform an appropriate parsing of the string to create the desired PHP type.
129 *
130 * @param string $string String to parse into PHP type
131 * @param string $type Type of value to return
132 *
133 * @return Appropriately typed interpretation of $string.
134 */
135 private function parse_env($string, $type)
136 {
137 $_ = $string;
138
139 switch ($type) {
140 case 'boolean':
141 $_ = (boolean) $_;
142 break;
143 case 'integer':
144 $_ = (integer) $_;
145 break;
146 case 'double':
147 $_ = (double) $_;
148 break;
149 case 'string':
150 break;
151 case 'array':
152 $_ = json_decode($_, true);
153 break;
154 case 'object':
155 $_ = json_decode($_, false);
156 break;
157 case 'resource':
158 case 'NULL':
159 default:
160 $_ = $this->parse_env($_, $this->guess_type($_));
161 }
162
163 return $_;
164 }
165
166 /**
167 * @brief Get environment variable value.
168 *
169 * Retrieve an environment variable's value or if it's not found, return the
170 * provided default value.
171 *
172 * @param string $varname Environment variable name
173 * @param mixed $default_value Default value to return if necessary
174 * @param string $type Type of value to return
175 *
176 * @return Value of the environment variable or default if not found.
177 */
178 private function getenv_default($varname, $default_value, $type = null)
179 {
180 $value = getenv($varname);
181
182 if ($value === false) {
183 $value = $default_value;
184 }
185 else {
186 $value = $this->parse_env($value, $type ?: gettype($default_value));
187 }
188
189 return $value;
190 }
191
192 /**
193 * Load config from local config file
194 *
195 * @todo Remove global $CONFIG
196 */
197 private function load()
198 {
199 // Load default settings
200 if (!$this->load_from_file('defaults.inc.php')) {
201 $this->errors[] = 'defaults.inc.php was not found.';
202 }
203
204 // load main config file
205 if (!$this->load_from_file('config.inc.php')) {
206 // Old configuration files
207 if (!$this->load_from_file('main.inc.php') || !$this->load_from_file('db.inc.php')) {
208 $this->errors[] = 'config.inc.php was not found.';
209 }
210 else if (rand(1,100) == 10) { // log warning on every 100th request (average)
211 trigger_error("config.inc.php was not found. Please migrate your config by running bin/update.sh", E_USER_WARNING);
212 }
213 }
214
215 // load host-specific configuration
216 $this->load_host_config();
217
218 // set skin (with fallback to old 'skin_path' property)
219 if (empty($this->prop['skin'])) {
220 if (!empty($this->prop['skin_path'])) {
221 $this->prop['skin'] = str_replace('skins/', '', unslashify($this->prop['skin_path']));
222 }
223 else {
224 $this->prop['skin'] = self::DEFAULT_SKIN;
225 }
226 }
227
228 // larry is the new default skin :-)
229 if ($this->prop['skin'] == 'default') {
230 $this->prop['skin'] = self::DEFAULT_SKIN;
231 }
232
233 // fix paths
234 foreach (array('log_dir' => 'logs', 'temp_dir' => 'temp') as $key => $dir) {
235 foreach (array($this->prop[$key], '../' . $this->prop[$key], RCUBE_INSTALL_PATH . $dir) as $path) {
236 if ($path && ($realpath = realpath(unslashify($path)))) {
237 $this->prop[$key] = $realpath;
238 break;
239 }
240 }
241 }
242
243 // fix default imap folders encoding
244 foreach (array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox') as $folder) {
245 $this->prop[$folder] = rcube_charset::convert($this->prop[$folder], RCUBE_CHARSET, 'UTF7-IMAP');
246 }
247
248 // set PHP error logging according to config
249 if ($this->prop['debug_level'] & 1) {
250 ini_set('log_errors', 1);
251
252 if ($this->prop['log_driver'] == 'syslog') {
253 ini_set('error_log', 'syslog');
254 }
255 else {
256 ini_set('error_log', $this->prop['log_dir'].'/errors');
257 }
258 }
259
260 // enable display_errors in 'show' level, but not for ajax requests
261 ini_set('display_errors', intval(empty($_REQUEST['_remote']) && ($this->prop['debug_level'] & 4)));
262
263 // remove deprecated properties
264 unset($this->prop['dst_active']);
265
266 // export config data
267 $GLOBALS['CONFIG'] = &$this->prop;
268 }
269
270 /**
271 * Load a host-specific config file if configured
272 * This will merge the host specific configuration with the given one
273 */
274 private function load_host_config()
275 {
276 if (empty($this->prop['include_host_config'])) {
277 return;
278 }
279
280 foreach (array('HTTP_HOST', 'SERVER_NAME', 'SERVER_ADDR') as $key) {
281 $fname = null;
282 $name = $_SERVER[$key];
283
284 if (!$name) {
285 continue;
286 }
287
288 if (is_array($this->prop['include_host_config'])) {
289 $fname = $this->prop['include_host_config'][$name];
290 }
291 else {
292 $fname = preg_replace('/[^a-z0-9\.\-_]/i', '', $name) . '.inc.php';
293 }
294
295 if ($fname && $this->load_from_file($fname)) {
296 return;
297 }
298 }
299 }
300
301 /**
302 * Read configuration from a file
303 * and merge with the already stored config values
304 *
305 * @param string $file Name of the config file to be loaded
306 *
307 * @return booelan True on success, false on failure
308 */
309 public function load_from_file($file)
310 {
311 $success = false;
312
313 foreach ($this->resolve_paths($file) as $fpath) {
314 if ($fpath && is_file($fpath) && is_readable($fpath)) {
315 // use output buffering, we don't need any output here
316 ob_start();
317 include($fpath);
318 ob_end_clean();
319
320 if (is_array($config)) {
321 $this->merge($config);
322 $success = true;
323 }
324 // deprecated name of config variable
325 if (is_array($rcmail_config)) {
326 $this->merge($rcmail_config);
327 $success = true;
328 }
329 }
330 }
331
332 return $success;
333 }
334
335 /**
336 * Helper method to resolve absolute paths to the given config file.
337 * This also takes the 'env' property into account.
338 *
339 * @param string $file Filename or absolute file path
340 * @param boolean $use_env Return -$env file path if exists
341 *
342 * @return array List of candidates in config dir path(s)
343 */
344 public function resolve_paths($file, $use_env = true)
345 {
346 $files = array();
347 $abs_path = rcube_utils::is_absolute_path($file);
348
349 foreach ($this->paths as $basepath) {
350 $realpath = $abs_path ? $file : realpath($basepath . '/' . $file);
351
352 // check if <file>-env.ini exists
353 if ($realpath && $use_env && !empty($this->env)) {
354 $envfile = preg_replace('/\.(inc.php)$/', '-' . $this->env . '.\\1', $realpath);
355 if (is_file($envfile)) {
356 $realpath = $envfile;
357 }
358 }
359
360 if ($realpath) {
361 $files[] = $realpath;
362
363 // no need to continue the loop if an absolute file path is given
364 if ($abs_path) {
365 break;
366 }
367 }
368 }
369
370 return $files;
371 }
372
373 /**
374 * Getter for a specific config parameter
375 *
376 * @param string $name Parameter name
377 * @param mixed $def Default value if not set
378 *
379 * @return mixed The requested config value
380 */
381 public function get($name, $def = null)
382 {
383 if (isset($this->prop[$name])) {
384 $result = $this->prop[$name];
385 }
386 else {
387 $result = $def;
388 }
389
390 $result = $this->getenv_default('ROUNDCUBE_' . strtoupper($name), $result);
391 $rcube = rcube::get_instance();
392
393 if ($name == 'timezone') {
394 if (empty($result) || $result == 'auto') {
395 $result = $this->client_timezone();
396 }
397 }
398 else if ($name == 'client_mimetypes') {
399 if (!$result && !$def) {
400 $result = 'text/plain,text/html,text/xml'
401 . ',image/jpeg,image/gif,image/png,image/bmp,image/tiff,image/webp'
402 . ',application/x-javascript,application/pdf,application/x-shockwave-flash';
403 }
404 if ($result && is_string($result)) {
405 $result = explode(',', $result);
406 }
407 }
408
409 $plugin = $rcube->plugins->exec_hook('config_get', array(
410 'name' => $name, 'default' => $def, 'result' => $result));
411
412 return $plugin['result'];
413 }
414
415 /**
416 * Setter for a config parameter
417 *
418 * @param string $name Parameter name
419 * @param mixed $value Parameter value
420 */
421 public function set($name, $value)
422 {
423 $this->prop[$name] = $value;
424 }
425
426 /**
427 * Override config options with the given values (eg. user prefs)
428 *
429 * @param array $prefs Hash array with config props to merge over
430 */
431 public function merge($prefs)
432 {
433 $prefs = $this->fix_legacy_props($prefs);
434 $this->prop = array_merge($this->prop, $prefs, $this->userprefs);
435 }
436
437 /**
438 * Merge the given prefs over the current config
439 * and make sure that they survive further merging.
440 *
441 * @param array $prefs Hash array with user prefs
442 */
443 public function set_user_prefs($prefs)
444 {
445 $prefs = $this->fix_legacy_props($prefs);
446
447 // Honor the dont_override setting for any existing user preferences
448 $dont_override = $this->get('dont_override');
449 if (is_array($dont_override) && !empty($dont_override)) {
450 foreach ($dont_override as $key) {
451 unset($prefs[$key]);
452 }
453 }
454
455 // larry is the new default skin :-)
456 if ($prefs['skin'] == 'default') {
457 $prefs['skin'] = self::DEFAULT_SKIN;
458 }
459
460 $this->userprefs = $prefs;
461 $this->prop = array_merge($this->prop, $prefs);
462 }
463
464 /**
465 * Getter for all config options
466 *
467 * @return array Hash array containing all config properties
468 */
469 public function all()
470 {
471 $props = $this->prop;
472
473 foreach ($props as $prop_name => $prop_value) {
474 $props[$prop_name] = $this->getenv_default('ROUNDCUBE_' . strtoupper($prop_name), $prop_value);
475 }
476
477 $rcube = rcube::get_instance();
478 $plugin = $rcube->plugins->exec_hook('config_get', array(
479 'name' => '*', 'result' => $props));
480
481 return $plugin['result'];
482 }
483
484 /**
485 * Special getter for user's timezone offset including DST
486 *
487 * @return float Timezone offset (in hours)
488 * @deprecated
489 */
490 public function get_timezone()
491 {
492 if ($tz = $this->get('timezone')) {
493 try {
494 $tz = new DateTimeZone($tz);
495 return $tz->getOffset(new DateTime('now')) / 3600;
496 }
497 catch (Exception $e) {
498 }
499 }
500
501 return 0;
502 }
503
504 /**
505 * Return requested DES crypto key.
506 *
507 * @param string $key Crypto key name
508 *
509 * @return string Crypto key
510 */
511 public function get_crypto_key($key)
512 {
513 // Bomb out if the requested key does not exist
514 if (!array_key_exists($key, $this->prop) || empty($this->prop[$key])) {
515 rcube::raise_error(array(
516 'code' => 500, 'type' => 'php',
517 'file' => __FILE__, 'line' => __LINE__,
518 'message' => "Request for unconfigured crypto key \"$key\""
519 ), true, true);
520 }
521
522 return $this->prop[$key];
523 }
524
525 /**
526 * Return configured crypto method.
527 *
528 * @return string Crypto method
529 */
530 public function get_crypto_method()
531 {
532 return $this->get('cipher_method') ?: 'DES-EDE3-CBC';
533 }
534
535 /**
536 * Try to autodetect operating system and find the correct line endings
537 *
538 * @return string The appropriate mail header delimiter
539 * @deprecated Since 1.3 we don't use mail()
540 */
541 public function header_delimiter()
542 {
543 // use the configured delimiter for headers
544 if (!empty($this->prop['mail_header_delimiter'])) {
545 $delim = $this->prop['mail_header_delimiter'];
546 if ($delim == "\n" || $delim == "\r\n") {
547 return $delim;
548 }
549 else {
550 rcube::raise_error(array(
551 'code' => 500, 'type' => 'php',
552 'file' => __FILE__, 'line' => __LINE__,
553 'message' => "Invalid mail_header_delimiter setting"
554 ), true, false);
555 }
556 }
557
558 $php_os = strtolower(substr(PHP_OS, 0, 3));
559
560 if ($php_os == 'win')
561 return "\r\n";
562
563 if ($php_os == 'mac')
564 return "\r\n";
565
566 return "\n";
567 }
568
569 /**
570 * Return the mail domain configured for the given host
571 *
572 * @param string $host IMAP host
573 * @param boolean $encode If true, domain name will be converted to IDN ASCII
574 *
575 * @return string Resolved SMTP host
576 */
577 public function mail_domain($host, $encode=true)
578 {
579 $domain = $host;
580
581 if (is_array($this->prop['mail_domain'])) {
582 if (isset($this->prop['mail_domain'][$host])) {
583 $domain = $this->prop['mail_domain'][$host];
584 }
585 }
586 else if (!empty($this->prop['mail_domain'])) {
587 $domain = rcube_utils::parse_host($this->prop['mail_domain']);
588 }
589
590 if ($encode) {
591 $domain = rcube_utils::idn_to_ascii($domain);
592 }
593
594 return $domain;
595 }
596
597 /**
598 * Getter for error state
599 *
600 * @return mixed Error message on error, False if no errors
601 */
602 public function get_error()
603 {
604 return empty($this->errors) ? false : join("\n", $this->errors);
605 }
606
607 /**
608 * Internal getter for client's (browser) timezone identifier
609 */
610 private function client_timezone()
611 {
612 // @TODO: remove this legacy timezone handling in the future
613 $props = $this->fix_legacy_props(array('timezone' => $_SESSION['timezone']));
614
615 if (!empty($props['timezone'])) {
616 try {
617 $tz = new DateTimeZone($props['timezone']);
618 return $tz->getName();
619 }
620 catch (Exception $e) { /* gracefully ignore */ }
621 }
622
623 // fallback to server's timezone
624 return date_default_timezone_get();
625 }
626
627 /**
628 * Convert legacy options into new ones
629 *
630 * @param array $props Hash array with config props
631 *
632 * @return array Converted config props
633 */
634 private function fix_legacy_props($props)
635 {
636 foreach ($this->legacy_props as $new => $old) {
637 if (isset($props[$old])) {
638 if (!isset($props[$new])) {
639 $props[$new] = $props[$old];
640 }
641 unset($props[$old]);
642 }
643 }
644
645 // convert deprecated numeric timezone value
646 if (isset($props['timezone']) && is_numeric($props['timezone'])) {
647 if ($tz = self::timezone_name_from_abbr($props['timezone'])) {
648 $props['timezone'] = $tz;
649 }
650 else {
651 unset($props['timezone']);
652 }
653 }
654
655 // translate old `preview_pane` settings to `layout`
656 if (isset($props['preview_pane']) && !isset($props['layout'])) {
657 $props['layout'] = $props['preview_pane'] ? 'desktop' : 'list';
658 unset($props['preview_pane']);
659 }
660
661 return $props;
662 }
663
664 /**
665 * timezone_name_from_abbr() replacement. Converts timezone offset
666 * into timezone name abbreviation.
667 *
668 * @param float $offset Timezone offset (in hours)
669 *
670 * @return string Timezone abbreviation
671 */
672 static public function timezone_name_from_abbr($offset)
673 {
674 // List of timezones here is not complete - https://bugs.php.net/bug.php?id=44780
675 if ($tz = timezone_name_from_abbr('', $offset * 3600, 0)) {
676 return $tz;
677 }
678
679 // try with more complete list (#1489261)
680 $timezones = array(
681 '-660' => "Pacific/Apia",
682 '-600' => "Pacific/Honolulu",
683 '-570' => "Pacific/Marquesas",
684 '-540' => "America/Anchorage",
685 '-480' => "America/Los_Angeles",
686 '-420' => "America/Denver",
687 '-360' => "America/Chicago",
688 '-300' => "America/New_York",
689 '-270' => "America/Caracas",
690 '-240' => "America/Halifax",
691 '-210' => "Canada/Newfoundland",
692 '-180' => "America/Sao_Paulo",
693 '-60' => "Atlantic/Azores",
694 '0' => "Europe/London",
695 '60' => "Europe/Paris",
696 '120' => "Europe/Helsinki",
697 '180' => "Europe/Moscow",
698 '210' => "Asia/Tehran",
699 '240' => "Asia/Dubai",
700 '270' => "Asia/Kabul",
701 '300' => "Asia/Karachi",
702 '330' => "Asia/Kolkata",
703 '345' => "Asia/Katmandu",
704 '360' => "Asia/Yekaterinburg",
705 '390' => "Asia/Rangoon",
706 '420' => "Asia/Krasnoyarsk",
707 '480' => "Asia/Shanghai",
708 '525' => "Australia/Eucla",
709 '540' => "Asia/Tokyo",
710 '570' => "Australia/Adelaide",
711 '600' => "Australia/Melbourne",
712 '630' => "Australia/Lord_Howe",
713 '660' => "Asia/Vladivostok",
714 '690' => "Pacific/Norfolk",
715 '720' => "Pacific/Auckland",
716 '765' => "Pacific/Chatham",
717 '780' => "Pacific/Enderbury",
718 '840' => "Pacific/Kiritimati",
719 );
720
721 return $timezones[(string) intval($offset * 60)];
722 }
723 }