Aleksander Machniak
2013-11-28 38f1f5692091ae9ddd60f27bc4960694947285b3
commit | author | age
197601 1 <?php
T 2
3 /*
4  +-----------------------------------------------------------------------+
e019f2 5  | This file is part of the Roundcube Webmail client                     |
fb5f52 6  | Copyright (C) 2008-2013, The Roundcube Dev Team                       |
7fe381 7  |                                                                       |
T 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.                     |
197601 11  |                                                                       |
T 12  | PURPOSE:                                                              |
13  |   Class to read configuration settings                                |
14  +-----------------------------------------------------------------------+
15  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
16  +-----------------------------------------------------------------------+
17 */
18
19 /**
e019f2 20  * Configuration class for Roundcube
197601 21  *
9ab346 22  * @package    Framework
AM 23  * @subpackage Core
197601 24  */
T 25 class rcube_config
26 {
aff970 27     const DEFAULT_SKIN = 'larry';
TB 28
deb2b8 29     private $env = '';
fb5f52 30     private $paths = array();
2eb794 31     private $prop = array();
A 32     private $errors = array();
33     private $userprefs = array();
0ac416 34
A 35     /**
36      * Renamed options
37      *
38      * @var array
39      */
40     private $legacy_props = array(
41         // new name => old name
42         'default_folders'      => 'default_imap_folders',
43         'mail_pagesize'        => 'pagesize',
44         'addressbook_pagesize' => 'pagesize',
651c7b 45         'reply_mode'           => 'top_posting',
f22654 46         'refresh_interval'     => 'keep_alive',
AM 47         'min_refresh_interval' => 'min_keep_alive',
67ac6e 48         'messages_cache_ttl'   => 'message_cache_lifetime',
df9d00 49         'redundant_attachments_cache_ttl' => 'redundant_attachments_memcache_ttl',
0ac416 50     );
197601 51
T 52
2eb794 53     /**
A 54      * Object constructor
deb2b8 55      *
TB 56      * @param string Environment suffix for config files to load
2eb794 57      */
deb2b8 58     public function __construct($env = '')
2471d3 59     {
deb2b8 60         $this->env = $env;
fb5f52 61
TB 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 (!$this->_is_absolute($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         }
deb2b8 87
2eb794 88         $this->load();
baecd8 89
TB 90         // Defaults, that we do not require you to configure,
91         // but contain information that is used in various
92         // locations in the code:
93         $this->set('contactlist_fields', array('name', 'firstname', 'surname', 'email'));
2471d3 94     }
2eb794 95
A 96
97     /**
98      * Load config from local config file
99      *
100      * @todo Remove global $CONFIG
101      */
102     private function load()
2471d3 103     {
461a30 104         // Load default settings
deb2b8 105         if (!$this->load_from_file('defaults.inc.php')) {
461a30 106             $this->errors[] = 'defaults.inc.php was not found.';
AM 107         }
2eb794 108
461a30 109         // load main config file
deb2b8 110         if (!$this->load_from_file('config.inc.php')) {
461a30 111             // Old configuration files
deb2b8 112             if (!$this->load_from_file('main.inc.php') ||
TB 113                 !$this->load_from_file('db.inc.php')) {
0f39b4 114                 $this->errors[] = 'config.inc.php was not found.';
TB 115             }
116             else if (rand(1,100) == 10) {  // log warning on every 100th request (average)
117                 trigger_error("config.inc.php was not found. Please migrate your config by running bin/update.sh", E_USER_WARNING);
118             }
461a30 119         }
f52c4f 120
2eb794 121         // load host-specific configuration
955a61 122         $this->load_host_config();
2eb794 123
A 124         // set skin (with fallback to old 'skin_path' property)
740875 125         if (empty($this->prop['skin'])) {
AM 126             if (!empty($this->prop['skin_path'])) {
127                 $this->prop['skin'] = str_replace('skins/', '', unslashify($this->prop['skin_path']));
128             }
129             else {
aff970 130                 $this->prop['skin'] = self::DEFAULT_SKIN;
740875 131             }
AM 132         }
9f1652 133
TB 134         // larry is the new default skin :-)
135         if ($this->prop['skin'] == 'default')
aff970 136             $this->prop['skin'] = self::DEFAULT_SKIN;
2eb794 137
A 138         // fix paths
9be2f4 139         $this->prop['log_dir'] = $this->prop['log_dir'] ? realpath(unslashify($this->prop['log_dir'])) : RCUBE_INSTALL_PATH . 'logs';
TB 140         $this->prop['temp_dir'] = $this->prop['temp_dir'] ? realpath(unslashify($this->prop['temp_dir'])) : RCUBE_INSTALL_PATH . 'temp';
f52c4f 141
2eb794 142         // fix default imap folders encoding
A 143         foreach (array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox') as $folder)
a92beb 144             $this->prop[$folder] = rcube_charset::convert($this->prop[$folder], RCUBE_CHARSET, 'UTF7-IMAP');
2eb794 145
c321a9 146         if (!empty($this->prop['default_folders']))
T 147             foreach ($this->prop['default_folders'] as $n => $folder)
a92beb 148                 $this->prop['default_folders'][$n] = rcube_charset::convert($folder, RCUBE_CHARSET, 'UTF7-IMAP');
2eb794 149
A 150         // set PHP error logging according to config
151         if ($this->prop['debug_level'] & 1) {
152             ini_set('log_errors', 1);
153
154             if ($this->prop['log_driver'] == 'syslog') {
155                 ini_set('error_log', 'syslog');
156             }
157             else {
158                 ini_set('error_log', $this->prop['log_dir'].'/errors');
159             }
160         }
23b495 161
A 162         // enable display_errors in 'show' level, but not for ajax requests
163         ini_set('display_errors', intval(empty($_REQUEST['_remote']) && ($this->prop['debug_level'] & 4)));
0829b7 164
5879c0 165         // remove deprecated properties
T 166         unset($this->prop['dst_active']);
2eb794 167
A 168         // export config data
169         $GLOBALS['CONFIG'] = &$this->prop;
2471d3 170     }
1854c4 171
2eb794 172     /**
A 173      * Load a host-specific config file if configured
174      * This will merge the host specific configuration with the given one
175      */
176     private function load_host_config()
177     {
955a61 178         if (empty($this->prop['include_host_config'])) {
AM 179             return;
2eb794 180         }
2471d3 181
955a61 182         foreach (array('HTTP_HOST', 'SERVER_NAME', 'SERVER_ADDR') as $key) {
AM 183             $fname = null;
184             $name  = $_SERVER[$key];
185
186             if (!$name) {
187                 continue;
188             }
189
190             if (is_array($this->prop['include_host_config'])) {
191                 $fname = $this->prop['include_host_config'][$name];
192             }
193             else {
194                 $fname = preg_replace('/[^a-z0-9\.\-_]/i', '', $name) . '.inc.php';
195             }
196
197             if ($fname && $this->load_from_file($fname)) {
198                 return;
199             }
2eb794 200         }
83a763 201     }
2eb794 202
A 203     /**
204      * Read configuration from a file
205      * and merge with the already stored config values
206      *
deb2b8 207      * @param string $file Name of the config file to be loaded
2eb794 208      * @return booelan True on success, false on failure
A 209      */
deb2b8 210     public function load_from_file($file)
2eb794 211     {
fb5f52 212         $success = false;
7c9850 213
fb5f52 214         foreach ($this->resolve_paths($file) as $fpath) {
TB 215             if ($fpath && is_file($fpath) && is_readable($fpath)) {
216                 // use output buffering, we don't need any output here 
217                 ob_start();
218                 include($fpath);
219                 ob_end_clean();
220
221                 if (is_array($config)) {
222                     $this->merge($config);
223                     $success = true;
224                 }
225                 // deprecated name of config variable
a315bf 226                 if (is_array($rcmail_config)) {
fb5f52 227                     $this->merge($rcmail_config);
TB 228                     $success = true;
229                 }
2eb794 230             }
A 231         }
232
fb5f52 233         return $success;
2eb794 234     }
A 235
deb2b8 236     /**
fb5f52 237      * Helper method to resolve absolute paths to the given config file.
deb2b8 238      * This also takes the 'env' property into account.
fb5f52 239      *
TB 240      * @param string  Filename or absolute file path
241      * @param boolean Return -$env file path if exists
242      * @return array  List of candidates in config dir path(s)
deb2b8 243      */
9e9d62 244     public function resolve_paths($file, $use_env = true)
deb2b8 245     {
fb5f52 246         $files = array();
TB 247         $abs_path = $this->_is_absolute($file);
248
249         foreach ($this->paths as $basepath) {
250             $realpath = $abs_path ? $file : realpath($basepath . '/' . $file);
251
252             // check if <file>-env.ini exists
253             if ($realpath && $use_env && !empty($this->env)) {
254                 $envfile = preg_replace('/\.(inc.php)$/', '-' . $this->env . '.\\1', $realpath);
255                 if (is_file($envfile))
256                     $realpath = $envfile;
257             }
258
259             if ($realpath) {
260                 $files[] = $realpath;
261
262                 // no need to continue the loop if an absolute file path is given
263                 if ($abs_path) {
264                     break;
265                 }
266             }
deb2b8 267         }
TB 268
fb5f52 269         return $files;
deb2b8 270     }
TB 271
fb5f52 272     /**
TB 273      * Determine whether the given file path is absolute or relative
274      */
275     private function _is_absolute($path)
276     {
277         return $path[0] == DIRECTORY_SEPARATOR || preg_match('!^[a-z]:[\\\\/]!i', $path);
278     }
2eb794 279
A 280     /**
281      * Getter for a specific config parameter
282      *
5c461b 283      * @param  string $name Parameter name
A 284      * @param  mixed  $def  Default value if not set
2eb794 285      * @return mixed  The requested config value
A 286      */
287     public function get($name, $def = null)
288     {
0ac416 289         if (isset($this->prop[$name])) {
A 290             $result = $this->prop[$name];
291         }
292         else {
293             $result = $def;
294         }
295
be98df 296         $rcube = rcube::get_instance();
0ac416 297
7ebe06 298         if ($name == 'timezone') {
AM 299             if (empty($result) || $result == 'auto') {
300                 $result = $this->client_timezone();
301             }
8fb4f0 302         }
TB 303         else if ($name == 'client_mimetypes') {
304             if ($result == null && $def == null)
305                 $result = 'text/plain,text/html,text/xml,image/jpeg,image/gif,image/png,image/bmp,image/tiff,application/x-javascript,application/pdf,application/x-shockwave-flash';
306             if ($result && is_string($result))
307                 $result = explode(',', $result);
308         }
183717 309
be98df 310         $plugin = $rcube->plugins->exec_hook('config_get', array(
A 311             'name' => $name, 'default' => $def, 'result' => $result));
183717 312
be98df 313         return $plugin['result'];
2eb794 314     }
A 315
316
317     /**
318      * Setter for a config parameter
319      *
5c461b 320      * @param string $name  Parameter name
A 321      * @param mixed  $value Parameter value
2eb794 322      */
A 323     public function set($name, $value)
324     {
325         $this->prop[$name] = $value;
326     }
327
328
329     /**
330      * Override config options with the given values (eg. user prefs)
331      *
5c461b 332      * @param array $prefs Hash array with config props to merge over
2eb794 333      */
A 334     public function merge($prefs)
335     {
993cb6 336         $prefs = $this->fix_legacy_props($prefs);
2eb794 337         $this->prop = array_merge($this->prop, $prefs, $this->userprefs);
A 338     }
339
340
341     /**
342      * Merge the given prefs over the current config
343      * and make sure that they survive further merging.
344      *
5c461b 345      * @param array $prefs Hash array with user prefs
2eb794 346      */
A 347     public function set_user_prefs($prefs)
348     {
993cb6 349         $prefs = $this->fix_legacy_props($prefs);
AM 350
bfb7d6 351         // Honor the dont_override setting for any existing user preferences
A 352         $dont_override = $this->get('dont_override');
353         if (is_array($dont_override) && !empty($dont_override)) {
9f1652 354             foreach ($dont_override as $key) {
TB 355                 unset($prefs[$key]);
bfb7d6 356             }
A 357         }
358
a9cbba 359         // larry is the new default skin :-)
TB 360         if ($prefs['skin'] == 'default') {
aff970 361             $prefs['skin'] = self::DEFAULT_SKIN;
a9cbba 362         }
TB 363
2eb794 364         $this->userprefs = $prefs;
bfb7d6 365         $this->prop      = array_merge($this->prop, $prefs);
2eb794 366     }
A 367
368
369     /**
370      * Getter for all config options
371      *
993cb6 372      * @return array  Hash array containing all config properties
2eb794 373      */
A 374     public function all()
375     {
38f1f5 376         $rcube  = rcube::get_instance();
AM 377         $plugin = $rcube->plugins->exec_hook('config_get', array(
378             'name' => '*', 'result' => $this->prop));
379
380         return $plugin['result'];
2eb794 381     }
A 382
da7178 383     /**
65082b 384      * Special getter for user's timezone offset including DST
T 385      *
386      * @return float  Timezone offset (in hours)
5879c0 387      * @deprecated
da7178 388      */
T 389     public function get_timezone()
390     {
e86a21 391       if ($tz = $this->get('timezone')) {
A 392         try {
393           $tz = new DateTimeZone($tz);
394           return $tz->getOffset(new DateTime('now')) / 3600;
395         }
396         catch (Exception $e) {
397         }
5879c0 398       }
T 399
400       return 0;
da7178 401     }
2eb794 402
A 403     /**
404      * Return requested DES crypto key.
405      *
5c461b 406      * @param string $key Crypto key name
2eb794 407      * @return string Crypto key
A 408      */
409     public function get_crypto_key($key)
410     {
411         // Bomb out if the requested key does not exist
412         if (!array_key_exists($key, $this->prop)) {
0c2596 413             rcube::raise_error(array(
2eb794 414                 'code' => 500, 'type' => 'php',
A 415                 'file' => __FILE__, 'line' => __LINE__,
416                 'message' => "Request for unconfigured crypto key \"$key\""
417             ), true, true);
418         }
419
420         $key = $this->prop[$key];
421
422         // Bomb out if the configured key is not exactly 24 bytes long
423         if (strlen($key) != 24) {
0c2596 424             rcube::raise_error(array(
2eb794 425                 'code' => 500, 'type' => 'php',
413df0 426                 'file' => __FILE__, 'line' => __LINE__,
2eb794 427                 'message' => "Configured crypto key '$key' is not exactly 24 bytes long"
A 428             ), true, true);
429         }
430
431         return $key;
432     }
433
434
435     /**
436      * Try to autodetect operating system and find the correct line endings
437      *
438      * @return string The appropriate mail header delimiter
439      */
440     public function header_delimiter()
441     {
442         // use the configured delimiter for headers
086767 443         if (!empty($this->prop['mail_header_delimiter'])) {
A 444             $delim = $this->prop['mail_header_delimiter'];
445             if ($delim == "\n" || $delim == "\r\n")
446                 return $delim;
447             else
0c2596 448                 rcube::raise_error(array(
086767 449                     'code' => 500, 'type' => 'php',
413df0 450                     'file' => __FILE__, 'line' => __LINE__,
086767 451                     'message' => "Invalid mail_header_delimiter setting"
A 452                 ), true, false);
453         }
2eb794 454
A 455         $php_os = strtolower(substr(PHP_OS, 0, 3));
456
457         if ($php_os == 'win')
458             return "\r\n";
459
460         if ($php_os == 'mac')
461             return "\r\n";
462
463         return "\n";
464     }
465
466
467     /**
468      * Return the mail domain configured for the given host
469      *
5c461b 470      * @param string  $host   IMAP host
A 471      * @param boolean $encode If true, domain name will be converted to IDN ASCII
2eb794 472      * @return string Resolved SMTP host
A 473      */
e99991 474     public function mail_domain($host, $encode=true)
2eb794 475     {
A 476         $domain = $host;
477
478         if (is_array($this->prop['mail_domain'])) {
479             if (isset($this->prop['mail_domain'][$host]))
480                 $domain = $this->prop['mail_domain'][$host];
481         }
1aceb9 482         else if (!empty($this->prop['mail_domain'])) {
A 483             $domain = rcube_utils::parse_host($this->prop['mail_domain']);
484         }
bb8721 485
1aceb9 486         if ($encode) {
A 487             $domain = rcube_utils::idn_to_ascii($domain);
488         }
e99991 489
2eb794 490         return $domain;
A 491     }
bb8721 492
A 493
2eb794 494     /**
A 495      * Getter for error state
496      *
497      * @return mixed Error message on error, False if no errors
498      */
499     public function get_error()
500     {
501         return empty($this->errors) ? false : join("\n", $this->errors);
502     }
83a763 503
5879c0 504
T 505     /**
506      * Internal getter for client's (browser) timezone identifier
507      */
508     private function client_timezone()
509     {
7ebe06 510         // @TODO: remove this legacy timezone handling in the future
AM 511         $props = $this->fix_legacy_props(array('timezone' => $_SESSION['timezone']));
512
513         if (!empty($props['timezone'])) {
086b15 514             try {
7ebe06 515                 $tz = new DateTimeZone($props['timezone']);
086b15 516                 return $tz->getName();
TB 517             }
518             catch (Exception $e) { /* gracefully ignore */ }
519         }
520
521         // fallback to server's timezone
522         return date_default_timezone_get();
5879c0 523     }
T 524
f65890 525     /**
AM 526      * Convert legacy options into new ones
993cb6 527      *
AM 528      * @param array $props Hash array with config props
529      *
530      * @return array Converted config props
f65890 531      */
993cb6 532     private function fix_legacy_props($props)
f65890 533     {
AM 534         foreach ($this->legacy_props as $new => $old) {
993cb6 535             if (isset($props[$old])) {
AM 536                 if (!isset($props[$new])) {
537                     $props[$new] = $props[$old];
f65890 538                 }
993cb6 539                 unset($props[$old]);
f65890 540             }
AM 541         }
993cb6 542
7ebe06 543         // convert deprecated numeric timezone value
AM 544         if (isset($props['timezone']) && is_numeric($props['timezone'])) {
8eb085 545             if ($tz = self::timezone_name_from_abbr($props['timezone'])) {
7ebe06 546                 $props['timezone'] = $tz;
AM 547             }
548             else {
549                 unset($props['timezone']);
550             }
551         }
552
993cb6 553         return $props;
f65890 554     }
8eb085 555
AM 556     /**
557      * timezone_name_from_abbr() replacement. Converts timezone offset
558      * into timezone name abbreviation.
559      *
560      * @param float $offset Timezone offset (in hours)
561      *
562      * @return string Timezone abbreviation
563      */
564     static public function timezone_name_from_abbr($offset)
565     {
566         // List of timezones here is not complete - https://bugs.php.net/bug.php?id=44780
567         if ($tz = timezone_name_from_abbr('', $offset * 3600, 0)) {
568             return $tz;
569         }
570
571         // try with more complete list (#1489261)
572         $timezones = array(
573             '-660' => "Pacific/Apia",
574             '-600' => "Pacific/Honolulu",
575             '-570' => "Pacific/Marquesas",
576             '-540' => "America/Anchorage",
577             '-480' => "America/Los_Angeles",
578             '-420' => "America/Denver",
579             '-360' => "America/Chicago",
580             '-300' => "America/New_York",
581             '-270' => "America/Caracas",
582             '-240' => "America/Halifax",
583             '-210' => "Canada/Newfoundland",
584             '-180' => "America/Sao_Paulo",
585              '-60' => "Atlantic/Azores",
586                '0' => "Europe/London",
587               '60' => "Europe/Paris",
588              '120' => "Europe/Helsinki",
589              '180' => "Europe/Moscow",
590              '210' => "Asia/Tehran",
591              '240' => "Asia/Dubai",
592              '300' => "Asia/Karachi",
593              '270' => "Asia/Kabul",
594              '300' => "Asia/Karachi",
595              '330' => "Asia/Kolkata",
596              '345' => "Asia/Katmandu",
597              '360' => "Asia/Yekaterinburg",
598              '390' => "Asia/Rangoon",
599              '420' => "Asia/Krasnoyarsk",
600              '480' => "Asia/Shanghai",
601              '525' => "Australia/Eucla",
602              '540' => "Asia/Tokyo",
603              '570' => "Australia/Adelaide",
604              '600' => "Australia/Melbourne",
605              '630' => "Australia/Lord_Howe",
606              '660' => "Asia/Vladivostok",
607              '690' => "Pacific/Norfolk",
608              '720' => "Pacific/Auckland",
609              '765' => "Pacific/Chatham",
610              '780' => "Pacific/Enderbury",
611              '840' => "Pacific/Kiritimati",
612         );
613
614         return $timezones[(string) intval($offset * 60)];
615     }
197601 616 }