| | Author: Aleksander Machniak | +-----------------------------------------------------------------------+ */ /** * Roundcube utilities * * @package Webmail * @subpackage Utils */ class rcmail_utils { public static $db; /** * Initialize database object and connect * * @return rcube_db Database instance */ public static function db() { if (self::$db === null) { $rc = rcube::get_instance(); $db = rcube_db::factory($rc->config->get('db_dsnw')); $db->set_debug((bool)$rc->config->get('sql_debug')); // Connect to database $db->db_connect('w'); if (!$db->is_connected()) { rcube::raise_error("Failed to connect to database", false, true); } self::$db = $db; } return self::$db; } /** * Initialize database schema * * @param string $dir Directory with sql files */ public static function db_init($dir) { $db = self::db(); $error = null; $file = $dir . '/' . $db->db_provider . '.initial.sql'; if (!file_exists($file)) { rcube::raise_error("DDL file $file not found", false, true); } echo "Creating database schema... "; if ($sql = file_get_contents($file)) { if (!$db->exec_script($sql)) { $error = $db->is_error(); } } else { $error = "Unable to read file $file or it is empty"; } if ($error) { echo "[FAILED]\n"; rcube::raise_error($error, false, true); } else { echo "[OK]\n"; } } /** * Update database schema * * @param string $dir Directory with sql files * @param string $package Component name * @param string $ver Optional current version number * @param array $opts Parameters (errors, quiet) * * @return True on success, False on failure */ public static function db_update($dir, $package, $ver = null, $opts = array()) { // Check if directory exists if (!file_exists($dir)) { if ($opts['errors']) { rcube::raise_error("Specified database schema directory doesn't exist.", false, true); } return false; } $db = self::db(); // Read DB schema version from database (if 'system' table exists) if (in_array($db->table_name('system'), (array)$db->list_tables())) { $version = self::db_version($package); } // DB version not found, but release version is specified if (empty($version) && $ver) { // Map old release version string to DB schema version // Note: This is for backward compat. only, do not need to be updated $map = array( '0.1-stable' => 1, '0.1.1' => 2008030300, '0.2-alpha' => 2008040500, '0.2-beta' => 2008060900, '0.2-stable' => 2008092100, '0.2.1' => 2008092100, '0.2.2' => 2008092100, '0.3-stable' => 2008092100, '0.3.1' => 2009090400, '0.4-beta' => 2009103100, '0.4' => 2010042300, '0.4.1' => 2010042300, '0.4.2' => 2010042300, '0.5-beta' => 2010100600, '0.5' => 2010100600, '0.5.1' => 2010100600, '0.5.2' => 2010100600, '0.5.3' => 2010100600, '0.5.4' => 2010100600, '0.6-beta' => 2011011200, '0.6' => 2011011200, '0.7-beta' => 2011092800, '0.7' => 2011111600, '0.7.1' => 2011111600, '0.7.2' => 2011111600, '0.7.3' => 2011111600, '0.7.4' => 2011111600, '0.8-beta' => 2011121400, '0.8-rc' => 2011121400, '0.8.0' => 2011121400, '0.8.1' => 2011121400, '0.8.2' => 2011121400, '0.8.3' => 2011121400, '0.8.4' => 2011121400, '0.8.5' => 2011121400, '0.8.6' => 2011121400, '0.9-beta' => 2012080700, ); $version = $map[$ver]; } // Assume last version before the 'system' table was added if (empty($version)) { $version = 2012080700; } $dir .= '/' . $db->db_provider; if (!file_exists($dir)) { if ($opts['errors']) { rcube::raise_error("DDL Upgrade files for " . $db->db_provider . " driver not found.", false, true); } return false; } $dh = opendir($dir); $result = array(); while ($file = readdir($dh)) { if (preg_match('/^([0-9]+)\.sql$/', $file, $m) && $m[1] > $version) { $result[] = $m[1]; } } sort($result, SORT_NUMERIC); foreach ($result as $v) { if (!$opts['quiet']) { echo "Updating database schema ($v)... "; } // Ignore errors here to print the error only once $db->set_option('ignore_errors', true); $error = self::db_update_schema($package, $v, "$dir/$v.sql"); $db->set_option('ignore_errors', false); if ($error) { if (!$opts['quiet']) { echo "[FAILED]\n"; } if ($opts['errors']) { rcube::raise_error("Error in DDL upgrade $v: $error", false, true); } return false; } else if (!$opts['quiet']) { echo "[OK]\n"; } } return true; } /** * Run database update from a single sql file */ protected static function db_update_schema($package, $version, $file) { $db = self::db(); // read DDL file if ($sql = file_get_contents($file)) { if (!$db->exec_script($sql)) { return $db->is_error(); } } // escape if 'system' table does not exist if ($version < 2013011000) { return; } $system_table = $db->table_name('system', true); $db->query("UPDATE " . $system_table . " SET `value` = ?" . " WHERE `name` = ?", $version, $package . '-version'); if (!$db->is_error() && !$db->affected_rows()) { $db->query("INSERT INTO " . $system_table ." (`name`, `value`) VALUES (?, ?)", $package . '-version', $version); } return $db->is_error(); } /** * Get version string for the specified package * * @param string $package Package name * * @return string Version string */ public static function db_version($package = 'roundcube') { $db = self::db(); $db->query("SELECT `value`" . " FROM " . $db->table_name('system', true) . " WHERE `name` = ?", $package . '-version'); $row = $db->fetch_array(); $version = preg_replace('/[^0-9]/', '', $row[0]); return $version; } /** * Removes all deleted records older than X days * * @param int $days Number of days */ public static function db_clean($days) { // mapping for table name => primary key $primary_keys = array( 'contacts' => 'contact_id', 'contactgroups' => 'contactgroup_id', ); $db = self::db(); $threshold = date('Y-m-d 00:00:00', time() - $days * 86400); foreach (array('contacts','contactgroups','identities') as $table) { $sqltable = $db->table_name($table, true); // also delete linked records // could be skipped for databases which respect foreign key constraints if ($db->db_provider == 'sqlite' && ($table == 'contacts' || $table == 'contactgroups')) { $pk = $primary_keys[$table]; $memberstable = $db->table_name('contactgroupmembers'); $db->query( "DELETE FROM " . $db->quote_identifier($memberstable) . " WHERE `$pk` IN (" . "SELECT `$pk` FROM $sqltable" . " WHERE `del` = 1 AND `changed` < ?" . ")", $threshold); echo $db->affected_rows() . " records deleted from '$memberstable'\n"; } // delete outdated records $db->query("DELETE FROM $sqltable WHERE `del` = 1 AND `changed` < ?", $threshold); echo $db->affected_rows() . " records deleted from '$table'\n"; } } /** * Reindex contacts */ public static function indexcontacts() { $db = self::db(); // iterate over all users $sql_result = $db->query("SELECT `user_id` FROM " . $db->table_name('users', true) . " ORDER BY `user_id`"); while ($sql_result && ($sql_arr = $db->fetch_assoc($sql_result))) { echo "Indexing contacts for user " . $sql_arr['user_id'] . "...\n"; $contacts = new rcube_contacts($db, $sql_arr['user_id']); $contacts->set_pagesize(9999); $result = $contacts->list_records(); while ($result->count && ($row = $result->next())) { unset($row['words']); $contacts->update($row['ID'], $row); } } echo "done.\n"; } /** * Modify user preferences * * @param string $name Option name * @param string $value Option value * @param int $userid Optional user identifier * @param string $type Optional value type (bool, int, string) */ public static function mod_pref($name, $value, $userid = null, $type = 'string') { $db = self::db(); if ($userid) { $query = '`user_id` = ' . intval($userid); } else { $query = '1=1'; } $type = strtolower($type); if ($type == 'bool' || $type == 'boolean') { $value = rcube_utils::get_boolean($value); } else if ($type == 'int' || $type == 'integer') { $value = (int) $value; } // iterate over all users $sql_result = $db->query("SELECT * FROM " . $db->table_name('users', true) . " WHERE $query"); while ($sql_result && ($sql_arr = $db->fetch_assoc($sql_result))) { echo "Updating prefs for user " . $sql_arr['user_id'] . "..."; $user = new rcube_user($sql_arr['user_id'], $sql_arr); $prefs = $old_prefs = $user->get_prefs(); $prefs[$name] = $value; if ($prefs != $old_prefs) { $user->save_prefs($prefs, true); echo "saved.\n"; } else { echo "nothing changed.\n"; } } } }