+ /**
+ * Merge the current configuration with the defaults
+ * and copy replaced values to the new options.
+ */
+ function merge_config()
+ {
+ $current = $this->config;
+ $this->config = array();
+ $this->load_defaults();
+
+ foreach ($this->replaced_config as $prop => $replacement) {
+ if (isset($current[$prop])) {
+ if ($prop == 'skin_path')
+ $this->config[$replacement] = preg_replace('#skins/(\w+)/?$#', '\\1', $current[$prop]);
+ else if ($prop == 'multiple_identities')
+ $this->config[$replacement] = $current[$prop] ? 2 : 0;
+ else
+ $this->config[$replacement] = $current[$prop];
+ }
+ unset($current[$prop]);
+ }
+
+ foreach ($this->obsolete_config as $prop) {
+ unset($current[$prop]);
+ }
+
+ // add all ldap_public sources having global_search enabled to autocomplete_addressbooks
+ if (is_array($current['ldap_public'])) {
+ foreach ($current['ldap_public'] as $key => $ldap_public) {
+ if ($ldap_public['global_search']) {
+ $this->config['autocomplete_addressbooks'][] = $key;
+ unset($current['ldap_public'][$key]['global_search']);
+ }
+ }
+ }
+
+ if ($current['keep_alive'] && $current['session_lifetime'] < $current['keep_alive'])
+ $current['session_lifetime'] = max(10, ceil($current['keep_alive'] / 60) * 2);
+
+ $this->config = array_merge($this->config, $current);
+
+ foreach ((array)$current['ldap_public'] as $key => $values) {
+ $this->config['ldap_public'][$key] = $current['ldap_public'][$key];
+ }
+ }
+
+ /**
+ * Compare the local database schema with the reference schema
+ * required for this version of Roundcube
+ *
+ * @param boolean True if the schema schould be updated
+ * @return boolean True if the schema is up-to-date, false if not or an error occured
+ */
+ function db_schema_check($DB, $update = false)
+ {
+ if (!$this->configured)
+ return false;
+
+ // read reference schema from mysql.initial.sql
+ $db_schema = $this->db_read_schema(INSTALL_PATH . 'SQL/mysql.initial.sql');
+ $errors = array();
+
+ // check list of tables
+ $existing_tables = $DB->list_tables();
+
+ foreach ($db_schema as $table => $cols) {
+ $table = !empty($this->config['db_table_'.$table]) ? $this->config['db_table_'.$table] : $table;
+ if (!in_array($table, $existing_tables)) {
+ $errors[] = "Missing table '".$table."'";
+ }
+ else { // compare cols
+ $db_cols = $DB->list_cols($table);
+ $diff = array_diff(array_keys($cols), $db_cols);
+ if (!empty($diff))
+ $errors[] = "Missing columns in table '$table': " . join(',', $diff);
+ }
+ }
+
+ return !empty($errors) ? $errors : false;
+ }
+
+ /**
+ * Utility function to read database schema from an .sql file
+ */
+ private function db_read_schema($schemafile)
+ {
+ $lines = file($schemafile);
+ $table_block = false;
+ $schema = array();
+ foreach ($lines as $line) {
+ if (preg_match('/^\s*create table `?([a-z0-9_]+)`?/i', $line, $m)) {
+ $table_block = $m[1];
+ }
+ else if ($table_block && preg_match('/^\s*`?([a-z0-9_-]+)`?\s+([a-z]+)/', $line, $m)) {
+ $col = $m[1];
+ if (!in_array(strtoupper($col), array('PRIMARY','KEY','INDEX','UNIQUE','CONSTRAINT','REFERENCES','FOREIGN'))) {
+ $schema[$table_block][$col] = $m[2];
+ }
+ }
+ }
+
+ return $schema;
+ }
+
+
+ /**
+ * Compare the local database schema with the reference schema
+ * required for this version of Roundcube
+ *
+ * @param boolean True if the schema schould be updated
+ * @return boolean True if the schema is up-to-date, false if not or an error occured
+ */
+ function mdb2_schema_check($update = false)
+ {
+ if (!$this->configured)
+ return false;
+
+ $options = array(
+ 'use_transactions' => false,
+ 'log_line_break' => "\n",
+ 'idxname_format' => '%s',
+ 'debug' => false,
+ 'quote_identifier' => true,
+ 'force_defaults' => false,
+ 'portability' => true
+ );
+
+ $dsnw = $this->config['db_dsnw'];
+ $schema = MDB2_Schema::factory($dsnw, $options);
+ $schema->db->supported['transactions'] = false;
+
+ if (PEAR::isError($schema)) {
+ $this->raise_error(array('code' => $schema->getCode(), 'message' => $schema->getMessage() . ' ' . $schema->getUserInfo()));
+ return false;
+ }
+ else {
+ $definition = $schema->getDefinitionFromDatabase();
+ $definition['charset'] = 'utf8';
+
+ if (PEAR::isError($definition)) {
+ $this->raise_error(array('code' => $definition->getCode(), 'message' => $definition->getMessage() . ' ' . $definition->getUserInfo()));
+ return false;
+ }
+
+ // load reference schema
+ $dsn_arr = MDB2::parseDSN($this->config['db_dsnw']);
+
+ $ref_schema = INSTALL_PATH . 'SQL/' . $dsn_arr['phptype'] . '.schema.xml';
+
+ if (is_readable($ref_schema)) {
+ $reference = $schema->parseDatabaseDefinition($ref_schema, false, array(), $schema->options['fail_on_invalid_names']);
+
+ if (PEAR::isError($reference)) {
+ $this->raise_error(array('code' => $reference->getCode(), 'message' => $reference->getMessage() . ' ' . $reference->getUserInfo()));
+ }
+ else {
+ $diff = $schema->compareDefinitions($reference, $definition);
+
+ if (empty($diff)) {
+ return true;
+ }
+ else if ($update) {
+ // update database schema with the diff from the above check
+ $success = $schema->alterDatabase($reference, $definition, $diff);
+
+ if (PEAR::isError($success)) {
+ $this->raise_error(array('code' => $success->getCode(), 'message' => $success->getMessage() . ' ' . $success->getUserInfo()));
+ }
+ else
+ return true;
+ }
+ echo '<pre>'; var_dump($diff); echo '</pre>';
+ return false;
+ }
+ }
+ else
+ $this->raise_error(array('message' => "Could not find reference schema file ($ref_schema)"));
+ return false;
+ }
+
+ return false;
+ }
+
+