1<?php 2 3/** 4 +-----------------------------------------------------------------------+ 5 | This file is part of the Roundcube Webmail client | 6 | | 7 | Copyright (C) The Roundcube Dev Team | 8 | | 9 | Licensed under the GNU General Public License version 3 or | 10 | any later version with exceptions for skins & plugins. | 11 | See the README file for a full license statement. | 12 | | 13 | PURPOSE: | 14 | Roundcube Installer | 15 +-----------------------------------------------------------------------+ 16 | Author: Thomas Bruederli <roundcube@gmail.com> | 17 | Author: Aleksander Machniak <alec@alec.pl> | 18 +-----------------------------------------------------------------------+ 19*/ 20 21/** 22 * Class to control the installation process of the Roundcube Webmail package 23 * 24 * @category Install 25 * @package Webmail 26 */ 27class rcmail_install 28{ 29 public $step; 30 public $last_error; 31 public $is_post = false; 32 public $failures = 0; 33 public $config = []; 34 public $defaults = []; 35 public $comments = []; 36 public $configured = false; 37 public $legacy_config = false; 38 public $email_pattern = '([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9])'; 39 public $bool_config_props = []; 40 41 public $local_config = ['db_dsnw', 'default_host', 'support_url', 'des_key', 'plugins']; 42 public $obsolete_config = ['db_backend', 'db_max_length', 'double_auth', 'preview_pane', 'debug_level', 'referer_check']; 43 public $replaced_config = [ 44 'skin_path' => 'skin', 45 'locale_string' => 'language', 46 'multiple_identities' => 'identities_level', 47 'addrbook_show_images' => 'show_images', 48 'imap_root' => 'imap_ns_personal', 49 'pagesize' => 'mail_pagesize', 50 'top_posting' => 'reply_mode', 51 'keep_alive' => 'refresh_interval', 52 'min_keep_alive' => 'min_refresh_interval', 53 ]; 54 55 // list of supported database drivers 56 public $supported_dbs = [ 57 'MySQL' => 'pdo_mysql', 58 'PostgreSQL' => 'pdo_pgsql', 59 'SQLite' => 'pdo_sqlite', 60 'SQLite (v2)' => 'pdo_sqlite2', 61 'SQL Server (SQLSRV)' => 'pdo_sqlsrv', 62 'SQL Server (DBLIB)' => 'pdo_dblib', 63 'Oracle' => 'oci8', 64 ]; 65 66 /** @var array List of config options with default value change per-release */ 67 public $defaults_changes = [ 68 '1.4.0' => ['skin', 'smtp_port', 'smtp_user', 'smtp_pass'], 69 '1.4.1' => ['jquery_ui_skin_map'], 70 ]; 71 72 /** 73 * Constructor 74 */ 75 public function __construct() 76 { 77 $this->step = isset($_REQUEST['_step']) ? intval($_REQUEST['_step']) : 0; 78 $this->is_post = isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST'; 79 } 80 81 /** 82 * Singleton getter 83 */ 84 public static function get_instance() 85 { 86 static $inst; 87 88 if (!$inst) { 89 $inst = new rcmail_install(); 90 } 91 92 return $inst; 93 } 94 95 /** 96 * Read the local config files and store properties 97 */ 98 public function load_config() 99 { 100 // defaults 101 if ($config = $this->load_config_file(RCUBE_CONFIG_DIR . 'defaults.inc.php')) { 102 $this->config = (array) $config; 103 $this->defaults = $this->config; 104 } 105 106 $config = null; 107 108 // config 109 if ($config = $this->load_config_file(RCUBE_CONFIG_DIR . 'config.inc.php')) { 110 $this->config = array_merge($this->config, $config); 111 } 112 else { 113 if ($config = $this->load_config_file(RCUBE_CONFIG_DIR . 'main.inc.php')) { 114 $this->config = array_merge($this->config, $config); 115 $this->legacy_config = true; 116 } 117 118 if ($config = $this->load_config_file(RCUBE_CONFIG_DIR . 'db.inc.php')) { 119 $this->config = array_merge($this->config, $config); 120 $this->legacy_config = true; 121 } 122 } 123 124 $this->configured = !empty($config); 125 } 126 127 /** 128 * Read the default config file and store properties 129 * 130 * @param string $file File name with path 131 */ 132 public function load_config_file($file) 133 { 134 if (!is_readable($file)) { 135 return; 136 } 137 138 $config = []; 139 $rcmail_config = []; // deprecated var name 140 141 include $file; 142 143 // read comments from config file 144 if (function_exists('token_get_all')) { 145 $tokens = token_get_all(file_get_contents($file)); 146 $in_config = false; 147 $buffer = ''; 148 149 for ($i = 0; $i < count($tokens); $i++) { 150 $token = $tokens[$i]; 151 if ($token[0] == T_VARIABLE && ($token[1] == '$config' || $token[1] == '$rcmail_config')) { 152 $in_config = true; 153 if ($buffer && $tokens[$i+1] == '[' && $tokens[$i+2][0] == T_CONSTANT_ENCAPSED_STRING) { 154 $propname = trim($tokens[$i+2][1], "'\""); 155 $this->comments[$propname] = $buffer; 156 $buffer = ''; 157 $i += 3; 158 } 159 } 160 else if ($in_config && $token[0] == T_COMMENT) { 161 $buffer .= strtr($token[1], ['\n' => "\n"]) . "\n"; 162 } 163 } 164 } 165 166 return array_merge((array) $rcmail_config, (array) $config); 167 } 168 169 /** 170 * Getter for a certain config property 171 * 172 * @param string $name Property name 173 * @param string $default Default value 174 * 175 * @return mixed The property value 176 */ 177 public function getprop($name, $default = '') 178 { 179 $value = isset($this->config[$name]) ? $this->config[$name] : null; 180 181 if ($name == 'des_key' && !$this->configured && !isset($_REQUEST["_$name"])) { 182 $value = rcube_utils::random_bytes(24); 183 } 184 185 return $value !== null && $value !== '' ? $value : $default; 186 } 187 188 /** 189 * Create configuration file that contains parameters 190 * that differ from default values. 191 * 192 * @return string The complete config file content 193 */ 194 public function create_config() 195 { 196 $config = []; 197 198 foreach ($this->config as $prop => $default) { 199 $is_default = !isset($_POST["_$prop"]); 200 $value = !$is_default || $this->bool_config_props[$prop] ? $_POST["_$prop"] : $default; 201 202 // always disable installer 203 if ($prop == 'enable_installer') { 204 $value = false; 205 } 206 207 // generate new encryption key, never use the default value 208 if ($prop == 'des_key' && $value == $this->defaults[$prop]) { 209 $value = rcube_utils::random_bytes(24); 210 } 211 212 // convert some form data 213 if ($prop == 'db_dsnw' && !empty($_POST['_dbtype'])) { 214 if ($_POST['_dbtype'] == 'sqlite') { 215 $value = sprintf('%s://%s?mode=0646', $_POST['_dbtype'], 216 $_POST['_dbname'][0] == '/' ? '/' . $_POST['_dbname'] : $_POST['_dbname']); 217 } 218 else if ($_POST['_dbtype']) { 219 $value = sprintf('%s://%s:%s@%s/%s', $_POST['_dbtype'], 220 rawurlencode($_POST['_dbuser']), rawurlencode($_POST['_dbpass']), $_POST['_dbhost'], $_POST['_dbname']); 221 } 222 } 223 else if ($prop == 'smtp_auth_type' && $value == '0') { 224 $value = ''; 225 } 226 else if ($prop == 'default_host' && is_array($value)) { 227 $value = self::_clean_array($value); 228 if (count($value) <= 1) { 229 $value = $value[0]; 230 } 231 } 232 else if ($prop == 'mail_pagesize' || $prop == 'addressbook_pagesize') { 233 $value = max(2, intval($value)); 234 } 235 else if ($prop == 'smtp_user' && !empty($_POST['_smtp_user_u'])) { 236 $value = '%u'; 237 } 238 else if ($prop == 'smtp_pass' && !empty($_POST['_smtp_user_u'])) { 239 $value = '%p'; 240 } 241 else if (is_bool($default)) { 242 $value = (bool) $value; 243 } 244 else if (is_numeric($value)) { 245 $value = intval($value); 246 } 247 else if ($prop == 'plugins' && !empty($_POST['submit'])) { 248 $value = []; 249 foreach (array_keys($_POST) as $key) { 250 if (preg_match('/^_plugins_*/', $key)) { 251 array_push($value, $_POST[$key]); 252 } 253 } 254 } 255 256 // skip this property 257 if ($value == $this->defaults[$prop] 258 && (!in_array($prop, $this->local_config) 259 || in_array($prop, array_merge($this->obsolete_config, array_keys($this->replaced_config))) 260 || preg_match('/^db_(table|sequence)_/', $prop) 261 ) 262 ) { 263 continue; 264 } 265 266 // save change 267 $this->config[$prop] = $value; 268 $config[$prop] = $value; 269 } 270 271 $out = "<?php\n\n"; 272 $out .= "/* Local configuration for Roundcube Webmail */\n\n"; 273 274 foreach ($config as $prop => $value) { 275 // copy option descriptions from existing config or defaults.inc.php 276 $out .= $this->comments[$prop]; 277 $out .= "\$config['$prop'] = " . self::_dump_var($value, $prop) . ";\n\n"; 278 } 279 280 return $out; 281 } 282 283 /** 284 * save generated config file in RCUBE_CONFIG_DIR 285 * 286 * @return boolean True if the file was saved successfully, false if not 287 */ 288 public function save_configfile($config) 289 { 290 if (is_writable(RCUBE_CONFIG_DIR)) { 291 return file_put_contents(RCUBE_CONFIG_DIR . 'config.inc.php', $config); 292 } 293 294 return false; 295 } 296 297 /** 298 * Check the current configuration for missing properties 299 * and deprecated or obsolete settings 300 * 301 * @param string $version Previous version on upgrade 302 * 303 * @return array List with problems detected 304 */ 305 public function check_config($version = null) 306 { 307 $this->load_config(); 308 309 if (!$this->configured) { 310 return; 311 } 312 313 $out = $seen = []; 314 315 // iterate over the current configuration 316 foreach (array_keys($this->config) as $prop) { 317 if (!empty($this->replaced_config[$prop])) { 318 $replacement = $this->replaced_config[$prop]; 319 $out['replaced'][] = ['prop' => $prop, 'replacement' => $replacement]; 320 $seen[$replacement] = true; 321 } 322 else if (empty($seen[$prop]) && in_array($prop, $this->obsolete_config)) { 323 $out['obsolete'][] = ['prop' => $prop]; 324 $seen[$prop] = true; 325 } 326 } 327 328 // the old default mime_magic reference is obsolete 329 if ($this->config['mime_magic'] == '/usr/share/misc/magic') { 330 $out['obsolete'][] = [ 331 'prop' => 'mime_magic', 332 'explain' => "Set value to null in order to use system default" 333 ]; 334 } 335 336 // check config dependencies and contradictions 337 if (!empty($this->config['enable_spellcheck']) && $this->config['spellcheck_engine'] == 'pspell') { 338 if (!extension_loaded('pspell')) { 339 $out['dependencies'][] = [ 340 'prop' => 'spellcheck_engine', 341 'explain' => "This requires the <tt>pspell</tt> extension which could not be loaded." 342 ]; 343 } 344 else if (!empty($this->config['spellcheck_languages'])) { 345 foreach ($this->config['spellcheck_languages'] as $lang => $descr) { 346 if (!@pspell_new($lang)) { 347 $out['dependencies'][] = [ 348 'prop' => 'spellcheck_languages', 349 'explain' => "You are missing pspell support for language $lang ($descr)" 350 ]; 351 } 352 } 353 } 354 } 355 356 if ($this->config['log_driver'] == 'syslog') { 357 if (!function_exists('openlog')) { 358 $out['dependencies'][] = [ 359 'prop' => 'log_driver', 360 'explain' => "This requires the <tt>syslog</tt> extension which could not be loaded." 361 ]; 362 } 363 364 if (empty($this->config['syslog_id'])) { 365 $out['dependencies'][] = [ 366 'prop' => 'syslog_id', 367 'explain' => "Using <tt>syslog</tt> for logging requires a syslog ID to be configured" 368 ]; 369 } 370 } 371 372 // check ldap_public sources having global_search enabled 373 if (is_array($this->config['ldap_public']) && !is_array($this->config['autocomplete_addressbooks'])) { 374 foreach ($this->config['ldap_public'] as $ldap_public) { 375 if ($ldap_public['global_search']) { 376 $out['replaced'][] = [ 377 'prop' => 'ldap_public::global_search', 378 'replacement' => 'autocomplete_addressbooks' 379 ]; 380 break; 381 } 382 } 383 } 384 385 if ($version) { 386 $out['defaults'] = []; 387 388 foreach ($this->defaults_changes as $v => $opts) { 389 if (version_compare($v, $version, '>')) { 390 $out['defaults'] = array_merge($out['defaults'], $opts); 391 } 392 } 393 394 $out['defaults'] = array_unique($out['defaults']); 395 } 396 397 return $out; 398 } 399 400 /** 401 * Merge the current configuration with the defaults 402 * and copy replaced values to the new options. 403 */ 404 public function merge_config() 405 { 406 $current = $this->config; 407 $this->config = []; 408 409 foreach ($this->replaced_config as $prop => $replacement) { 410 if (isset($current[$prop])) { 411 if ($prop == 'skin_path') { 412 $this->config[$replacement] = preg_replace('#skins/(\w+)/?$#', '\\1', $current[$prop]); 413 } 414 else if ($prop == 'multiple_identities') { 415 $this->config[$replacement] = $current[$prop] ? 2 : 0; 416 } 417 else { 418 $this->config[$replacement] = $current[$prop]; 419 } 420 } 421 422 unset($current[$prop]); 423 } 424 425 foreach ($this->obsolete_config as $prop) { 426 unset($current[$prop]); 427 } 428 429 // add all ldap_public sources having global_search enabled to autocomplete_addressbooks 430 if (is_array($current['ldap_public'])) { 431 foreach ($current['ldap_public'] as $key => $ldap_public) { 432 if ($ldap_public['global_search']) { 433 $this->config['autocomplete_addressbooks'][] = $key; 434 unset($current['ldap_public'][$key]['global_search']); 435 } 436 } 437 } 438 439 $this->config = array_merge($this->config, $current); 440 441 foreach (array_keys((array) $current['ldap_public']) as $key) { 442 $this->config['ldap_public'][$key] = $current['ldap_public'][$key]; 443 } 444 } 445 446 /** 447 * Compare the local database schema with the reference schema 448 * required for this version of Roundcube 449 * 450 * @param rcube_db $db Database object 451 * 452 * @return bool True if the schema is up-to-date, false if not or an error occurred 453 */ 454 public function db_schema_check($db) 455 { 456 if (!$this->configured) { 457 return false; 458 } 459 460 // read reference schema from mysql.initial.sql 461 $engine = $db->db_provider; 462 $db_schema = $this->db_read_schema(INSTALL_PATH . "SQL/$engine.initial.sql", $schema_version); 463 $errors = []; 464 465 // Just check the version 466 if ($schema_version) { 467 $version = rcmail_utils::db_version(); 468 469 if (empty($version)) { 470 $errors[] = "Schema version not found"; 471 } 472 else if ($schema_version != $version) { 473 $errors[] = "Schema version: {$version} (required: {$schema_version})"; 474 } 475 476 return !empty($errors) ? $errors : false; 477 } 478 479 // check list of tables 480 $existing_tables = $db->list_tables(); 481 482 foreach ($db_schema as $table => $cols) { 483 $table = $this->config['db_prefix'] . $table; 484 485 if (!in_array($table, $existing_tables)) { 486 $errors[] = "Missing table '".$table."'"; 487 } 488 else { // compare cols 489 $db_cols = $db->list_cols($table); 490 $diff = array_diff(array_keys($cols), $db_cols); 491 492 if (!empty($diff)) { 493 $errors[] = "Missing columns in table '$table': " . implode(',', $diff); 494 } 495 } 496 } 497 498 return !empty($errors) ? $errors : false; 499 } 500 501 /** 502 * Utility function to read database schema from an .sql file 503 */ 504 private function db_read_schema($schemafile, &$version = null) 505 { 506 $lines = file($schemafile); 507 $schema = []; 508 $keywords = ['PRIMARY','KEY','INDEX','UNIQUE','CONSTRAINT','REFERENCES','FOREIGN']; 509 $table_name = null; 510 511 foreach ($lines as $line) { 512 if (preg_match('/^\s*create table ([\S]+)/i', $line, $m)) { 513 $table_name = explode('.', $m[1]); 514 $table_name = end($table_name); 515 $table_name = preg_replace('/[`"\[\]]/', '', $table_name); 516 } 517 else if (preg_match('/insert into/i', $line) && preg_match('/\'roundcube-version\',\s*\'([0-9]+)\'/', $line, $m)) { 518 $version = $m[1]; 519 } 520 else if ($table_name && ($line = trim($line))) { 521 if ($line == 'GO' || $line[0] == ')' || $line[strlen($line)-1] == ';') { 522 $table_name = null; 523 } 524 else { 525 $items = explode(' ', $line); 526 $col = $items[0]; 527 $col = preg_replace('/[`"\[\]]/', '', $col); 528 529 if (!in_array(strtoupper($col), $keywords)) { 530 $type = strtolower($items[1]); 531 $type = preg_replace('/[^a-zA-Z0-9()]/', '', $type); 532 533 $schema[$table_name][$col] = $type; 534 } 535 } 536 } 537 } 538 539 return $schema; 540 } 541 542 /** 543 * Try to detect some file's mimetypes to test the correct behavior of fileinfo 544 */ 545 public function check_mime_detection() 546 { 547 $errors = []; 548 $files = [ 549 'program/resources/tinymce/video.png' => 'image/png', 550 'program/resources/blank.tiff' => 'image/tiff', 551 'program/resources/blocked.gif' => 'image/gif', 552 ]; 553 554 foreach ($files as $path => $expected) { 555 $mimetype = rcube_mime::file_content_type(INSTALL_PATH . $path, basename($path)); 556 if ($mimetype != $expected) { 557 $errors[] = [$path, $mimetype, $expected]; 558 } 559 } 560 561 return $errors; 562 } 563 564 /** 565 * Check the correct configuration of the 'mime_types' mapping option 566 */ 567 public function check_mime_extensions() 568 { 569 $errors = []; 570 $types = [ 571 'application/zip' => 'zip', 572 'text/css' => 'css', 573 'application/pdf' => 'pdf', 574 'image/gif' => 'gif', 575 'image/svg+xml' => 'svg', 576 ]; 577 578 foreach ($types as $mimetype => $expected) { 579 $ext = rcube_mime::get_mime_extensions($mimetype); 580 if (!in_array($expected, (array) $ext)) { 581 $errors[] = [$mimetype, $ext, $expected]; 582 } 583 } 584 585 return $errors; 586 } 587 588 /** 589 * Getter for the last error message 590 * 591 * @return string Error message or null if none exists 592 */ 593 public function get_error() 594 { 595 return $this->last_error['message']; 596 } 597 598 /** 599 * Return a list with all imap/smtp hosts configured 600 * 601 * @return array Clean list with imap/smtp hosts 602 */ 603 public function get_hostlist($prop = 'default_host') 604 { 605 $hosts = (array) $this->getprop($prop); 606 $out = []; 607 $imap_host = ''; 608 609 if ($prop == 'smtp_server') { 610 // Set the imap host name for the %h macro 611 $default_hosts = $this->get_hostlist(); 612 $imap_host = !empty($default_hosts) ? $default_hosts[0] : ''; 613 } 614 615 foreach ($hosts as $key => $name) { 616 if (!empty($name)) { 617 if ($prop == 'smtp_server') { 618 // SMTP host array uses `IMAP host => SMTP host` format 619 $host = $name; 620 } 621 else { 622 $host = is_numeric($key) ? $name : $key; 623 } 624 625 $out[] = rcube_utils::parse_host($host, $imap_host); 626 } 627 } 628 629 return $out; 630 } 631 632 /** 633 * Create a HTML dropdown to select a previous version of Roundcube 634 */ 635 public function versions_select($attrib = []) 636 { 637 $select = new html_select($attrib); 638 $select->add([ 639 '0.1-stable', '0.1.1', 640 '0.2-alpha', '0.2-beta', '0.2-stable', 641 '0.3-stable', '0.3.1', 642 '0.4-beta', '0.4.2', 643 '0.5-beta', '0.5', '0.5.1', '0.5.2', '0.5.3', '0.5.4', 644 '0.6-beta', '0.6', 645 '0.7-beta', '0.7', '0.7.1', '0.7.2', '0.7.3', '0.7.4', 646 '0.8-beta', '0.8-rc', '0.8.0', '0.8.1', '0.8.2', '0.8.3', '0.8.4', '0.8.5', '0.8.6', 647 '0.9-beta', '0.9-rc', '0.9-rc2', 648 // Note: Do not add newer versions here 649 ]); 650 651 return $select; 652 } 653 654 /** 655 * Return a list with available subfolders of the skin directory 656 * 657 * @return array List of available skins 658 */ 659 public function list_skins() 660 { 661 $skins = []; 662 $skindir = INSTALL_PATH . 'skins/'; 663 664 foreach (glob($skindir . '*') as $path) { 665 if (is_dir($path) && is_readable($path)) { 666 $skins[] = substr($path, strlen($skindir)); 667 } 668 } 669 670 return $skins; 671 } 672 673 /** 674 * Return a list with available subfolders of the plugins directory 675 * (with their associated description in composer.json) 676 * 677 * @return array List of available plugins 678 */ 679 public function list_plugins() 680 { 681 $plugins = []; 682 $plugin_dir = INSTALL_PATH . 'plugins/'; 683 $enabled = isset($this->config['plugins']) ? (array) $this->config['plugins'] : []; 684 685 foreach (glob($plugin_dir . '*') as $path) { 686 if (!is_dir($path)) { 687 continue; 688 } 689 690 if (is_readable($path . '/composer.json')) { 691 $file_json = json_decode(file_get_contents($path . '/composer.json')); 692 $plugin_desc = $file_json->description ?: 'N/A'; 693 } 694 else { 695 $plugin_desc = 'N/A'; 696 } 697 698 $name = substr($path, strlen($plugin_dir)); 699 $plugins[] = [ 700 'name' => $name, 701 'desc' => $plugin_desc, 702 'enabled' => in_array($name, $enabled) 703 ]; 704 } 705 706 return $plugins; 707 } 708 709 /** 710 * Display OK status 711 * 712 * @param string $name Test name 713 * @param string $message Confirm message 714 */ 715 public function pass($name, $message = '') 716 { 717 echo rcube::Q($name) . ': <span class="success">OK</span>'; 718 $this->_showhint($message); 719 } 720 721 /** 722 * Display an error status and increase failure count 723 * 724 * @param string $name Test name 725 * @param string $message Error message 726 * @param string $url URL for details 727 * @param bool $optional Do not count this failure 728 */ 729 public function fail($name, $message = '', $url = '', $optional = false) 730 { 731 if (!$optional) { 732 $this->failures++; 733 } 734 735 echo rcube::Q($name) . ': <span class="fail">NOT OK</span>'; 736 $this->_showhint($message, $url); 737 } 738 739 /** 740 * Display an error status for optional settings/features 741 * 742 * @param string $name Test name 743 * @param string $message Error message 744 * @param string $url URL for details 745 */ 746 public function optfail($name, $message = '', $url = '') 747 { 748 echo rcube::Q($name) . ': <span class="na">NOT OK</span>'; 749 $this->_showhint($message, $url); 750 } 751 752 /** 753 * Display warning status 754 * 755 * @param string $name Test name 756 * @param string $message Warning message 757 * @param string $url URL for details 758 */ 759 public function na($name, $message = '', $url = '') 760 { 761 echo rcube::Q($name) . ': <span class="na">NOT AVAILABLE</span>'; 762 $this->_showhint($message, $url); 763 } 764 765 private function _showhint($message, $url = '') 766 { 767 $hint = rcube::Q($message); 768 769 if ($url) { 770 $hint .= ($hint ? '; ' : '') . 'See <a href="' . rcube::Q($url) . '" target="_blank">' . rcube::Q($url) . '</a>'; 771 } 772 773 if ($hint) { 774 echo '<span class="indent">(' . $hint . ')</span>'; 775 } 776 } 777 778 private static function _clean_array($arr) 779 { 780 $out = []; 781 782 foreach (array_unique($arr) as $k => $val) { 783 if (!empty($val)) { 784 if (is_numeric($k)) { 785 $out[] = $val; 786 } 787 else { 788 $out[$k] = $val; 789 } 790 } 791 } 792 793 return $out; 794 } 795 796 private static function _dump_var($var, $name = null) 797 { 798 // special values 799 switch ($name) { 800 case 'syslog_facility': 801 $list = [ 802 32 => 'LOG_AUTH', 80 => 'LOG_AUTHPRIV', 72 => ' LOG_CRON', 803 24 => 'LOG_DAEMON', 0 => 'LOG_KERN', 128 => 'LOG_LOCAL0', 804 136 => 'LOG_LOCAL1', 144 => 'LOG_LOCAL2', 152 => 'LOG_LOCAL3', 805 160 => 'LOG_LOCAL4', 168 => 'LOG_LOCAL5', 176 => 'LOG_LOCAL6', 806 184 => 'LOG_LOCAL7', 48 => 'LOG_LPR', 16 => 'LOG_MAIL', 807 56 => 'LOG_NEWS', 40 => 'LOG_SYSLOG', 8 => 'LOG_USER', 64 => 'LOG_UUCP' 808 ]; 809 810 if (!empty($list[$var])) { 811 return $list[$var]; 812 } 813 814 break; 815 } 816 817 if (is_array($var)) { 818 if (empty($var)) { 819 return '[]'; 820 } 821 // check if all keys are numeric 822 $isnum = true; 823 foreach (array_keys($var) as $key) { 824 if (!is_numeric($key)) { 825 $isnum = false; 826 break; 827 } 828 } 829 830 if ($isnum) { 831 return '[' . implode(', ', array_map(['rcmail_install', '_dump_var'], $var)) . ']'; 832 } 833 } 834 835 return var_export($var, true); 836 } 837 838 /** 839 * Initialize the database with the according schema 840 * 841 * @param rcube_db $db Database connection 842 * 843 * @return bool True on success, False on error 844 */ 845 public function init_db($db) 846 { 847 $engine = $db->db_provider; 848 849 // read schema file from /SQL/* 850 $fname = INSTALL_PATH . "SQL/$engine.initial.sql"; 851 if ($sql = @file_get_contents($fname)) { 852 $db->set_option('table_prefix', $this->config['db_prefix']); 853 $db->exec_script($sql); 854 } 855 else { 856 $this->fail('DB Schema', "Cannot read the schema file: $fname"); 857 return false; 858 } 859 860 if ($err = $this->get_error()) { 861 $this->fail('DB Schema', "Error creating database schema: $err"); 862 return false; 863 } 864 865 return true; 866 } 867 868 /** 869 * Update database schema 870 * 871 * @param string $version Version to update from 872 * 873 * @return boolean True on success, False on error 874 */ 875 public function update_db($version) 876 { 877 return rcmail_utils::db_update(INSTALL_PATH . 'SQL', 'roundcube', $version, ['quiet' => true]); 878 } 879 880 /** 881 * Handler for Roundcube errors 882 */ 883 public function raise_error($p) 884 { 885 $this->last_error = $p; 886 } 887} 888