Mercurial > hg > rc2
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 } |