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