David Carter
2013-08-30 de8687f9f11d49d36b322af72f644f7c5a232b9f
commit | author | age
197601 1 <?php
T 2
3 /*
4  +-----------------------------------------------------------------------+
e019f2 5  | This file is part of the Roundcube Webmail client                     |
0ac416 6  | Copyright (C) 2008-2012, 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 = '';
TB 30     private $basedir = 'config/';
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;
TB 61         $this->basedir = RCUBE_CONFIG_DIR;
62
2eb794 63         $this->load();
baecd8 64
TB 65         // Defaults, that we do not require you to configure,
66         // but contain information that is used in various
67         // locations in the code:
68         $this->set('contactlist_fields', array('name', 'firstname', 'surname', 'email'));
2471d3 69     }
2eb794 70
A 71
72     /**
73      * Load config from local config file
74      *
75      * @todo Remove global $CONFIG
76      */
77     private function load()
2471d3 78     {
461a30 79         // Load default settings
deb2b8 80         if (!$this->load_from_file('defaults.inc.php')) {
461a30 81             $this->errors[] = 'defaults.inc.php was not found.';
AM 82         }
2eb794 83
461a30 84         // load main config file
deb2b8 85         if (!$this->load_from_file('config.inc.php')) {
461a30 86             // Old configuration files
deb2b8 87             if (!$this->load_from_file('main.inc.php') ||
TB 88                 !$this->load_from_file('db.inc.php')) {
0f39b4 89                 $this->errors[] = 'config.inc.php was not found.';
TB 90             }
91             else if (rand(1,100) == 10) {  // log warning on every 100th request (average)
92                 trigger_error("config.inc.php was not found. Please migrate your config by running bin/update.sh", E_USER_WARNING);
93             }
461a30 94         }
f52c4f 95
2eb794 96         // load host-specific configuration
deb2b8 97         if (!empty($_SERVER['HTTP_HOST']))
TB 98             $this->load_host_config();
2eb794 99
A 100         // set skin (with fallback to old 'skin_path' property)
740875 101         if (empty($this->prop['skin'])) {
AM 102             if (!empty($this->prop['skin_path'])) {
103                 $this->prop['skin'] = str_replace('skins/', '', unslashify($this->prop['skin_path']));
104             }
105             else {
aff970 106                 $this->prop['skin'] = self::DEFAULT_SKIN;
740875 107             }
AM 108         }
9f1652 109
TB 110         // larry is the new default skin :-)
111         if ($this->prop['skin'] == 'default')
aff970 112             $this->prop['skin'] = self::DEFAULT_SKIN;
2eb794 113
A 114         // fix paths
9be2f4 115         $this->prop['log_dir'] = $this->prop['log_dir'] ? realpath(unslashify($this->prop['log_dir'])) : RCUBE_INSTALL_PATH . 'logs';
TB 116         $this->prop['temp_dir'] = $this->prop['temp_dir'] ? realpath(unslashify($this->prop['temp_dir'])) : RCUBE_INSTALL_PATH . 'temp';
f52c4f 117
2eb794 118         // fix default imap folders encoding
A 119         foreach (array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox') as $folder)
a92beb 120             $this->prop[$folder] = rcube_charset::convert($this->prop[$folder], RCUBE_CHARSET, 'UTF7-IMAP');
2eb794 121
c321a9 122         if (!empty($this->prop['default_folders']))
T 123             foreach ($this->prop['default_folders'] as $n => $folder)
a92beb 124                 $this->prop['default_folders'][$n] = rcube_charset::convert($folder, RCUBE_CHARSET, 'UTF7-IMAP');
2eb794 125
A 126         // set PHP error logging according to config
127         if ($this->prop['debug_level'] & 1) {
128             ini_set('log_errors', 1);
129
130             if ($this->prop['log_driver'] == 'syslog') {
131                 ini_set('error_log', 'syslog');
132             }
133             else {
134                 ini_set('error_log', $this->prop['log_dir'].'/errors');
135             }
136         }
23b495 137
A 138         // enable display_errors in 'show' level, but not for ajax requests
139         ini_set('display_errors', intval(empty($_REQUEST['_remote']) && ($this->prop['debug_level'] & 4)));
0829b7 140
65082b 141         // set timezone auto settings values
T 142         if ($this->prop['timezone'] == 'auto') {
5879c0 143           $this->prop['_timezone_value'] = $this->client_timezone();
0829b7 144         }
7bcd29 145         else if (is_numeric($this->prop['timezone']) && ($tz = timezone_name_from_abbr("", $this->prop['timezone'] * 3600, 0))) {
TB 146           $this->prop['timezone'] = $tz;
147         }
148         else if (empty($this->prop['timezone'])) {
149           $this->prop['timezone'] = 'UTC';
65082b 150         }
5879c0 151
T 152         // remove deprecated properties
153         unset($this->prop['dst_active']);
2eb794 154
A 155         // export config data
156         $GLOBALS['CONFIG'] = &$this->prop;
2471d3 157     }
1854c4 158
2eb794 159     /**
A 160      * Load a host-specific config file if configured
161      * This will merge the host specific configuration with the given one
162      */
163     private function load_host_config()
164     {
165         $fname = null;
10eedb 166
2eb794 167         if (is_array($this->prop['include_host_config'])) {
A 168             $fname = $this->prop['include_host_config'][$_SERVER['HTTP_HOST']];
169         }
170         else if (!empty($this->prop['include_host_config'])) {
171             $fname = preg_replace('/[^a-z0-9\.\-_]/i', '', $_SERVER['HTTP_HOST']) . '.inc.php';
172         }
2471d3 173
2eb794 174         if ($fname) {
deb2b8 175             $this->load_from_file($fname);
2eb794 176         }
83a763 177     }
T 178
2eb794 179
A 180     /**
181      * Read configuration from a file
182      * and merge with the already stored config values
183      *
deb2b8 184      * @param string $file Name of the config file to be loaded
2eb794 185      * @return booelan True on success, false on failure
A 186      */
deb2b8 187     public function load_from_file($file)
2eb794 188     {
deb2b8 189         $fpath = $this->resolve_path($file);
TB 190         if ($fpath && is_file($fpath) && is_readable($fpath)) {
7c9850 191             // use output buffering, we don't need any output here 
A 192             ob_start();
2eb794 193             include($fpath);
7c9850 194             ob_end_clean();
A 195
461a30 196             if (is_array($config)) {
AM 197                 $this->merge($config);
198                 return true;
199             }
200             // deprecated name of config variable
201             else if (is_array($rcmail_config)) {
f65890 202                 $this->merge($rcmail_config);
2eb794 203                 return true;
A 204             }
205         }
206
207         return false;
208     }
209
deb2b8 210     /**
TB 211      * Helper method to resolve the absolute path to the given config file.
212      * This also takes the 'env' property into account.
213      */
214     public function resolve_path($file, $use_env = true)
215     {
216         if (strpos($file, '/') === false) {
217             $file = realpath($this->basedir . '/' . $file);
218         }
219
220         // check if <file>-env.ini exists
221         if ($file && $use_env && !empty($this->env)) {
222             $envfile = preg_replace('/\.(inc.php)$/', '-' . $this->env . '.\\1', $file);
223             if (is_file($envfile))
224                 return $envfile;
225         }
226
227         return $file;
228     }
229
2eb794 230
A 231     /**
232      * Getter for a specific config parameter
233      *
5c461b 234      * @param  string $name Parameter name
A 235      * @param  mixed  $def  Default value if not set
2eb794 236      * @return mixed  The requested config value
A 237      */
238     public function get($name, $def = null)
239     {
0ac416 240         if (isset($this->prop[$name])) {
A 241             $result = $this->prop[$name];
242         }
243         else {
244             $result = $def;
245         }
246
be98df 247         $rcube = rcube::get_instance();
0ac416 248
8fb4f0 249         if ($name == 'timezone' && isset($this->prop['_timezone_value'])) {
801b69 250             $result = $this->prop['_timezone_value'];
8fb4f0 251         }
TB 252         else if ($name == 'client_mimetypes') {
253             if ($result == null && $def == null)
254                 $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';
255             if ($result && is_string($result))
256                 $result = explode(',', $result);
257         }
183717 258
be98df 259         $plugin = $rcube->plugins->exec_hook('config_get', array(
A 260             'name' => $name, 'default' => $def, 'result' => $result));
183717 261
be98df 262         return $plugin['result'];
2eb794 263     }
A 264
265
266     /**
267      * Setter for a config parameter
268      *
5c461b 269      * @param string $name  Parameter name
A 270      * @param mixed  $value Parameter value
2eb794 271      */
A 272     public function set($name, $value)
273     {
274         $this->prop[$name] = $value;
275     }
276
277
278     /**
279      * Override config options with the given values (eg. user prefs)
280      *
5c461b 281      * @param array $prefs Hash array with config props to merge over
2eb794 282      */
A 283     public function merge($prefs)
284     {
285         $this->prop = array_merge($this->prop, $prefs, $this->userprefs);
f65890 286         $this->fix_legacy_props();
2eb794 287     }
A 288
289
290     /**
291      * Merge the given prefs over the current config
292      * and make sure that they survive further merging.
293      *
5c461b 294      * @param array $prefs Hash array with user prefs
2eb794 295      */
A 296     public function set_user_prefs($prefs)
297     {
bfb7d6 298         // Honor the dont_override setting for any existing user preferences
A 299         $dont_override = $this->get('dont_override');
300         if (is_array($dont_override) && !empty($dont_override)) {
9f1652 301             foreach ($dont_override as $key) {
TB 302                 unset($prefs[$key]);
bfb7d6 303             }
A 304         }
305
c21d7f 306         // convert user's timezone into the new format
7bcd29 307         if (is_numeric($prefs['timezone']) && ($tz = timezone_name_from_abbr('', $prefs['timezone'] * 3600, 0))) {
TB 308             $prefs['timezone'] = $tz;
c21d7f 309         }
A 310
a9cbba 311         // larry is the new default skin :-)
TB 312         if ($prefs['skin'] == 'default') {
aff970 313             $prefs['skin'] = self::DEFAULT_SKIN;
a9cbba 314         }
TB 315
2eb794 316         $this->userprefs = $prefs;
bfb7d6 317         $this->prop      = array_merge($this->prop, $prefs);
f65890 318
AM 319         $this->fix_legacy_props();
65082b 320
T 321         // override timezone settings with client values
801b69 322         if ($this->prop['timezone'] == 'auto') {
5879c0 323             $this->prop['_timezone_value'] = isset($_SESSION['timezone']) ? $this->client_timezone() : $this->prop['_timezone_value'];
65082b 324         }
985e41 325         else if (isset($this->prop['_timezone_value']))
T 326            unset($this->prop['_timezone_value']);
2eb794 327     }
A 328
329
330     /**
331      * Getter for all config options
332      *
333      * @return array  Hash array containg all config properties
334      */
335     public function all()
336     {
337         return $this->prop;
338     }
339
da7178 340     /**
65082b 341      * Special getter for user's timezone offset including DST
T 342      *
343      * @return float  Timezone offset (in hours)
5879c0 344      * @deprecated
da7178 345      */
T 346     public function get_timezone()
347     {
e86a21 348       if ($tz = $this->get('timezone')) {
A 349         try {
350           $tz = new DateTimeZone($tz);
351           return $tz->getOffset(new DateTime('now')) / 3600;
352         }
353         catch (Exception $e) {
354         }
5879c0 355       }
T 356
357       return 0;
da7178 358     }
2eb794 359
A 360     /**
361      * Return requested DES crypto key.
362      *
5c461b 363      * @param string $key Crypto key name
2eb794 364      * @return string Crypto key
A 365      */
366     public function get_crypto_key($key)
367     {
368         // Bomb out if the requested key does not exist
369         if (!array_key_exists($key, $this->prop)) {
0c2596 370             rcube::raise_error(array(
2eb794 371                 'code' => 500, 'type' => 'php',
A 372                 'file' => __FILE__, 'line' => __LINE__,
373                 'message' => "Request for unconfigured crypto key \"$key\""
374             ), true, true);
375         }
376
377         $key = $this->prop[$key];
378
379         // Bomb out if the configured key is not exactly 24 bytes long
380         if (strlen($key) != 24) {
0c2596 381             rcube::raise_error(array(
2eb794 382                 'code' => 500, 'type' => 'php',
413df0 383                 'file' => __FILE__, 'line' => __LINE__,
2eb794 384                 'message' => "Configured crypto key '$key' is not exactly 24 bytes long"
A 385             ), true, true);
386         }
387
388         return $key;
389     }
390
391
392     /**
393      * Try to autodetect operating system and find the correct line endings
394      *
395      * @return string The appropriate mail header delimiter
396      */
397     public function header_delimiter()
398     {
399         // use the configured delimiter for headers
086767 400         if (!empty($this->prop['mail_header_delimiter'])) {
A 401             $delim = $this->prop['mail_header_delimiter'];
402             if ($delim == "\n" || $delim == "\r\n")
403                 return $delim;
404             else
0c2596 405                 rcube::raise_error(array(
086767 406                     'code' => 500, 'type' => 'php',
413df0 407                     'file' => __FILE__, 'line' => __LINE__,
086767 408                     'message' => "Invalid mail_header_delimiter setting"
A 409                 ), true, false);
410         }
2eb794 411
A 412         $php_os = strtolower(substr(PHP_OS, 0, 3));
413
414         if ($php_os == 'win')
415             return "\r\n";
416
417         if ($php_os == 'mac')
418             return "\r\n";
419
420         return "\n";
421     }
422
423
424     /**
425      * Return the mail domain configured for the given host
426      *
5c461b 427      * @param string  $host   IMAP host
A 428      * @param boolean $encode If true, domain name will be converted to IDN ASCII
2eb794 429      * @return string Resolved SMTP host
A 430      */
e99991 431     public function mail_domain($host, $encode=true)
2eb794 432     {
A 433         $domain = $host;
434
435         if (is_array($this->prop['mail_domain'])) {
436             if (isset($this->prop['mail_domain'][$host]))
437                 $domain = $this->prop['mail_domain'][$host];
438         }
1aceb9 439         else if (!empty($this->prop['mail_domain'])) {
A 440             $domain = rcube_utils::parse_host($this->prop['mail_domain']);
441         }
bb8721 442
1aceb9 443         if ($encode) {
A 444             $domain = rcube_utils::idn_to_ascii($domain);
445         }
e99991 446
2eb794 447         return $domain;
A 448     }
bb8721 449
A 450
2eb794 451     /**
A 452      * Getter for error state
453      *
454      * @return mixed Error message on error, False if no errors
455      */
456     public function get_error()
457     {
458         return empty($this->errors) ? false : join("\n", $this->errors);
459     }
83a763 460
5879c0 461
T 462     /**
463      * Internal getter for client's (browser) timezone identifier
464      */
465     private function client_timezone()
466     {
086b15 467         if (isset($_SESSION['timezone']) && is_numeric($_SESSION['timezone'])
TB 468               && ($ctz = timezone_name_from_abbr("", $_SESSION['timezone'] * 3600, 0))) {
469             return $ctz;
470         }
471         else if (!empty($_SESSION['timezone'])) {
472             try {
473                 $tz = timezone_open($_SESSION['timezone']);
474                 return $tz->getName();
475             }
476             catch (Exception $e) { /* gracefully ignore */ }
477         }
478
479         // fallback to server's timezone
480         return date_default_timezone_get();
5879c0 481     }
T 482
f65890 483     /**
AM 484      * Convert legacy options into new ones
485      */
486     private function fix_legacy_props()
487     {
488         foreach ($this->legacy_props as $new => $old) {
489             if (isset($this->prop[$old])) {
490                 if (!isset($this->prop[$new])) {
491                     $this->prop[$new] = $this->prop[$old];
492                 }
493                 unset($this->prop[$old]);
494             }
495         }
496     }
197601 497 }