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