From 66510e20d36cb8da4f3012ef063de7bfce9b51aa Mon Sep 17 00:00:00 2001 From: Aleksander Machniak <alec@alec.pl> Date: Wed, 09 May 2012 07:52:28 -0400 Subject: [PATCH] Merge pull request #1 from Brianetta/de-svn --- program/include/rcube_mdb2.php | 465 ++++++++++++++++++++++++++++++++++++++------------------- 1 files changed, 310 insertions(+), 155 deletions(-) diff --git a/program/include/rcube_mdb2.php b/program/include/rcube_mdb2.php index f58c32b..7c431df 100644 --- a/program/include/rcube_mdb2.php +++ b/program/include/rcube_mdb2.php @@ -6,7 +6,10 @@ | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2009, The Roundcube Dev Team | - | Licensed under the GNU GPL | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | | | | PURPOSE: | | PEAR:DB wrapper class that implements PEAR MDB2 functions | @@ -35,15 +38,16 @@ */ class rcube_mdb2 { - var $db_dsnw; // DSN for write operations - var $db_dsnr; // DSN for read operations - var $db_connected = false; // Already connected ? - var $db_mode = ''; // Connection mode - var $db_handle = 0; // Connection handle - var $db_error = false; - var $db_error_msg = ''; + public $db_dsnw; // DSN for write operations + public $db_dsnr; // DSN for read operations + public $db_connected = false; // Already connected ? + public $db_mode = ''; // Connection mode + public $db_handle = 0; // Connection handle + public $db_error = false; + public $db_error_msg = ''; private $debug_mode = false; + private $conn_failure = false; private $a_query_results = array('dummy'); private $last_res_id = 0; private $tables; @@ -55,10 +59,11 @@ * @param string $db_dsnw DSN for read/write operations * @param string $db_dsnr Optional DSN for read only operations */ - function __construct($db_dsnw, $db_dsnr='', $pconn=false) + public function __construct($db_dsnw, $db_dsnr='', $pconn=false) { - if ($db_dsnr == '') + if (empty($db_dsnr)) { $db_dsnr = $db_dsnw; + } $this->db_dsnw = $db_dsnw; $this->db_dsnr = $db_dsnr; @@ -83,13 +88,16 @@ 'persistent' => $this->db_pconn, 'emulate_prepared' => $this->debug_mode, 'debug' => $this->debug_mode, - 'debug_handler' => 'mdb2_debug_handler', - 'portability' => MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_EMPTY_TO_NULL); + 'debug_handler' => array($this, 'debug_handler'), + 'portability' => MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_EMPTY_TO_NULL, + ); if ($this->db_provider == 'pgsql') { $db_options['disable_smart_seqname'] = true; $db_options['seqname_format'] = '%s'; } + $this->db_error = false; + $this->db_error_msg = null; $dbh = MDB2::connect($dsn, $db_options); @@ -97,17 +105,19 @@ $this->db_error = true; $this->db_error_msg = $dbh->getMessage(); - raise_error(array('code' => 500, 'type' => 'db', + rcube::raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, 'message' => $dbh->getUserInfo()), true, false); } else if ($this->db_provider == 'sqlite') { $dsn_array = MDB2::parseDSN($dsn); - if (!filesize($dsn_array['database']) && !empty($this->sqlite_initials)) - $this->_sqlite_create_database($dbh, $this->sqlite_initials); + if (!filesize($dsn_array['database']) && !empty($this->sqlite_initials)) { + $this->sqlite_create_database($dbh, $this->sqlite_initials); + } } - else if ($this->db_provider!='mssql' && $this->db_provider!='sqlsrv') + else if ($this->db_provider != 'mssql' && $this->db_provider != 'sqlsrv') { $dbh->setCharset('utf8'); + } return $dbh; } @@ -117,34 +127,45 @@ * Connect to appropiate database depending on the operation * * @param string $mode Connection mode (r|w) - * @access public */ - function db_connect($mode) + public function db_connect($mode) { + // previous connection failed, don't attempt to connect again + if ($this->conn_failure) { + return; + } + + // no replication + if ($this->db_dsnw == $this->db_dsnr) { + $mode = 'w'; + } + // Already connected if ($this->db_connected) { - // connected to read-write db, current connection is ok - if ($this->db_mode == 'w') - return; - - // no replication, current connection is ok for read and write - if (empty($this->db_dsnr) || $this->db_dsnw == $this->db_dsnr) { - $this->db_mode = 'w'; + // connected to db with the same or "higher" mode + if ($this->db_mode == 'w' || $this->db_mode == $mode) { return; } - - // Same mode, current connection is ok - if ($this->db_mode == $mode) - return; } $dsn = ($mode == 'r') ? $this->db_dsnr : $this->db_dsnw; - $this->db_handle = $this->dsn_connect($dsn); + $this->db_handle = $this->dsn_connect($dsn); $this->db_connected = !PEAR::isError($this->db_handle); - if ($this->db_connected) - $this->db_mode = $mode; + // use write-master when read-only fails + if (!$this->db_connected && $mode == 'r') { + $mode = 'w'; + $this->db_handle = $this->dsn_connect($this->db_dsnw); + $this->db_connected = !PEAR::isError($this->db_handle); + } + + if ($this->db_connected) { + $this->db_mode = $mode; + } + else { + $this->conn_failure = true; + } } @@ -152,9 +173,8 @@ * Activate/deactivate debug mode * * @param boolean $dbg True if SQL queries should be logged - * @access public */ - function set_debug($dbg = true) + public function set_debug($dbg = true) { $this->debug_mode = $dbg; if ($this->db_connected) { @@ -168,9 +188,8 @@ * Getter for error state * * @param boolean True on error - * @access public */ - function is_error() + public function is_error() { return $this->db_error ? $this->db_error_msg : false; } @@ -180,11 +199,20 @@ * Connection state checker * * @param boolean True if in connected state - * @access public */ - function is_connected() + public function is_connected() { return PEAR::isError($this->db_handle) ? false : $this->db_connected; + } + + + /** + * Is database replication configured? + * This returns true if dsnw != dsnr + */ + public function is_replicated() + { + return !empty($this->db_dsnr) && $this->db_dsnw != $this->db_dsnr; } @@ -193,17 +221,18 @@ * * @param string SQL query to execute * @param mixed Values to be inserted in query + * * @return number Query handle identifier - * @access public */ - function query() + public function query() { $params = func_get_args(); $query = array_shift($params); // Support one argument of type array, instead of n arguments - if (count($params) == 1 && is_array($params[0])) + if (count($params) == 1 && is_array($params[0])) { $params = $params[0]; + } return $this->_query($query, 0, 0, $params); } @@ -216,10 +245,10 @@ * @param number Offset for LIMIT statement * @param number Number of rows for LIMIT statement * @param mixed Values to be inserted in query + * * @return number Query handle identifier - * @access public */ - function limitquery() + public function limitquery() { $params = func_get_args(); $query = array_shift($params); @@ -248,17 +277,21 @@ $this->db_connect($mode); // check connection before proceeding - if (!$this->is_connected()) + if (!$this->is_connected()) { return null; + } - if ($this->db_provider == 'sqlite') - $this->_sqlite_prepare(); + if ($this->db_provider == 'sqlite') { + $this->sqlite_prepare(); + } - if ($numrows || $offset) + if ($numrows || $offset) { $result = $this->db_handle->setLimit($numrows,$offset); + } - if (empty($params)) + if (empty($params)) { $result = $mode == 'r' ? $this->db_handle->query($query) : $this->db_handle->exec($query); + } else { $params = (array)$params; $q = $this->db_handle->prepare($query, null, $mode=='w' ? MDB2_PREPARE_MANIP : null); @@ -266,10 +299,10 @@ $this->db_error = true; $this->db_error_msg = $q->userinfo; - raise_error(array('code' => 500, 'type' => 'db', + rcube::raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, 'message' => $this->db_error_msg), true, false); - + $result = false; } else { @@ -289,17 +322,18 @@ * * @param number $res_id Optional query handle identifier * @return mixed Number of rows or false on failure - * @access public */ - function num_rows($res_id=null) + public function num_rows($res_id=null) { - if (!$this->db_connected) + if (!$this->db_connected) { return false; + } - if ($result = $this->_get_result($res_id)) + if ($result = $this->_get_result($res_id)) { return $result->numRows(); - else - return false; + } + + return false; } @@ -308,14 +342,14 @@ * * @param number $res_id Optional query handle identifier * @return mixed Number of rows or false on failure - * @access public */ - function affected_rows($res_id = null) + public function affected_rows($res_id = null) { - if (!$this->db_connected) + if (!$this->db_connected) { return false; + } - return (int) $this->_get_result($res_id); + return $this->_get_result($res_id); } @@ -324,21 +358,24 @@ * For Postgres databases, a sequence name is required * * @param string $table Table name (to find the incremented sequence) + * * @return mixed ID or false on failure - * @access public */ - function insert_id($table = '') + public function insert_id($table = '') { - if (!$this->db_connected || $this->db_mode == 'r') + if (!$this->db_connected || $this->db_mode == 'r') { return false; + } if ($table) { - if ($this->db_provider == 'pgsql') + if ($this->db_provider == 'pgsql') { // find sequence name - $table = get_sequence_name($table); - else + $table = $this->sequence_name($table); + } + else { // resolve table name - $table = get_table_name($table); + $table = $this->table_name($table); + } } $id = $this->db_handle->lastInsertID($table); @@ -352,10 +389,10 @@ * If no query handle is specified, the last query will be taken as reference * * @param number $res_id Optional query handle identifier + * * @return mixed Array with col values or false on failure - * @access public */ - function fetch_assoc($res_id=null) + public function fetch_assoc($res_id = null) { $result = $this->_get_result($res_id); return $this->_fetch_row($result, MDB2_FETCHMODE_ASSOC); @@ -367,10 +404,10 @@ * If no query handle is specified, the last query will be taken as reference * * @param number $res_id Optional query handle identifier + * * @return mixed Array with col values or false on failure - * @access public */ - function fetch_array($res_id=null) + public function fetch_array($res_id = null) { $result = $this->_get_result($res_id); return $this->_fetch_row($result, MDB2_FETCHMODE_ORDERED); @@ -382,13 +419,14 @@ * * @param MDB2_Result_Common Query $result result handle * @param number $mode Fetch mode identifier - * @return mixed Array with col values or false on failure - * @access private + * + * @return mixed Array with col values or false on failure */ private function _fetch_row($result, $mode) { - if ($result === false || PEAR::isError($result) || !$this->is_connected()) + if ($result === false || PEAR::isError($result) || !$this->is_connected()) { return false; + } return $result->fetchRow($mode); } @@ -398,18 +436,19 @@ * Wrapper for the SHOW TABLES command * * @return array List of all tables of the current database - * @access public * @since 0.4-beta */ - function list_tables() + public function list_tables() { // get tables if not cached if (!$this->tables) { $this->db_handle->loadModule('Manager'); - if (!PEAR::isError($result = $this->db_handle->listTables())) + if (!PEAR::isError($result = $this->db_handle->listTables())) { $this->tables = $result; - else + } + else { $this->tables = array(); + } } return $this->tables; @@ -420,15 +459,16 @@ * Wrapper for SHOW COLUMNS command * * @param string Table name + * * @return array List of table cols */ - function list_cols($table) + public function list_cols($table) { $this->db_handle->loadModule('Manager'); if (!PEAR::isError($result = $this->db_handle->listTableFields($table))) { return $result; } - + return null; } @@ -438,18 +478,20 @@ * * @param mixed $input Value to quote * @param string $type Type of data + * * @return string Quoted/converted string for use in query - * @access public */ - function quote($input, $type = null) + public function quote($input, $type = null) { // handle int directly for better performance - if ($type == 'integer') + if ($type == 'integer') { return intval($input); + } // create DB handle if not available - if (!$this->db_handle) + if (!$this->db_handle) { $this->db_connect('r'); + } return $this->db_connected ? $this->db_handle->quote($input, $type) : addslashes($input); } @@ -459,12 +501,12 @@ * Quotes a string so it can be safely used as a table or column name * * @param string $str Value to quote + * * @return string Quoted string for use in query * @deprecated Replaced by rcube_MDB2::quote_identifier * @see rcube_mdb2::quote_identifier - * @access public */ - function quoteIdentifier($str) + public function quoteIdentifier($str) { return $this->quote_identifier($str); } @@ -474,13 +516,14 @@ * Quotes a string so it can be safely used as a table or column name * * @param string $str Value to quote + * * @return string Quoted string for use in query - * @access public */ - function quote_identifier($str) + public function quote_identifier($str) { - if (!$this->db_handle) + if (!$this->db_handle) { $this->db_connect('r'); + } return $this->db_connected ? $this->db_handle->quoteIdentifier($str) : $str; } @@ -490,14 +533,15 @@ * Escapes a string * * @param string $str The string to be escaped + * * @return string The escaped string - * @access public * @since 0.1.1 */ - function escapeSimple($str) + public function escapeSimple($str) { - if (!$this->db_handle) + if (!$this->db_handle) { $this->db_connect('r'); + } return $this->db_handle->escape($str); } @@ -507,11 +551,10 @@ * Return SQL function for current time and date * * @return string SQL function to use in query - * @access public */ - function now() + public function now() { - switch($this->db_provider) { + switch ($this->db_provider) { case 'mssql': case 'sqlsrv': return "getdate()"; @@ -527,16 +570,18 @@ * * @param array $arr Input array * @param string $type Type of data + * * @return string Comma-separated list of quoted values for use in query - * @access public */ - function array2list($arr, $type = null) + public function array2list($arr, $type = null) { - if (!is_array($arr)) + if (!is_array($arr)) { return $this->quote($arr, $type); + } - foreach ($arr as $idx => $item) + foreach ($arr as $idx => $item) { $arr[$idx] = $this->quote($item, $type); + } return implode(',', $arr); } @@ -545,11 +590,15 @@ /** * Return SQL statement to convert a field value into a unix timestamp * + * This method is deprecated and should not be used anymore due to limitations + * of timestamp functions in Mysql (year 2038 problem) + * * @param string $field Field name + * * @return string SQL statement to use in query - * @access public + * @deprecated */ - function unixtimestamp($field) + public function unixtimestamp($field) { switch($this->db_provider) { case 'pgsql': @@ -569,10 +618,10 @@ * Return SQL statement to convert from a unix timestamp * * @param string $timestamp Field name + * * @return string SQL statement to use in query - * @access public */ - function fromunixtime($timestamp) + public function fromunixtime($timestamp) { return date("'Y-m-d H:i:s'", $timestamp); } @@ -583,13 +632,13 @@ * * @param string $column Field name * @param string $value Search value + * * @return string SQL statement to use in query - * @access public */ - function ilike($column, $value) + public function ilike($column, $value) { // TODO: use MDB2's matchPattern() function - switch($this->db_provider) { + switch ($this->db_provider) { case 'pgsql': return $this->quote_identifier($column).' ILIKE '.$this->quote($value); default: @@ -599,23 +648,55 @@ /** + * Abstract SQL statement for value concatenation + * + * @return string SQL statement to be used in query + */ + public function concat(/* col1, col2, ... */) + { + $func = ''; + $args = func_get_args(); + if (is_array($args[0])) + $args = $args[0]; + + switch ($this->db_provider) { + case 'mysql': + case 'mysqli': + $func = 'CONCAT'; + $delim = ', '; + break; + case 'mssql': + case 'sqlsrv': + $delim = ' + '; + break; + default: + $delim = ' || '; + } + + return $func . '(' . join($delim, $args) . ')'; + } + + + /** * Encodes non-UTF-8 characters in string/array/object (recursive) * * @param mixed $input Data to fix + * * @return mixed Properly UTF-8 encoded data - * @access public */ - function encode($input) + public static function encode($input) { if (is_object($input)) { - foreach (get_object_vars($input) as $idx => $value) - $input->$idx = $this->encode($value); + foreach (get_object_vars($input) as $idx => $value) { + $input->$idx = self::encode($value); + } return $input; } else if (is_array($input)) { - foreach ($input as $idx => $value) - $input[$idx] = $this->encode($value); - return $input; + foreach ($input as $idx => $value) { + $input[$idx] = self::encode($value); + } + return $input; } return utf8_encode($input); @@ -626,20 +707,22 @@ * Decodes encoded UTF-8 string/object/array (recursive) * * @param mixed $input Input data + * * @return mixed Decoded data - * @access public */ - function decode($input) + public static function decode($input) { if (is_object($input)) { - foreach (get_object_vars($input) as $idx => $value) - $input->$idx = $this->decode($value); + foreach (get_object_vars($input) as $idx => $value) { + $input->$idx = self::decode($value); + } return $input; } else if (is_array($input)) { - foreach ($input as $idx => $value) - $input[$idx] = $this->decode($value); - return $input; + foreach ($input as $idx => $value) { + $input[$idx] = self::decode($value); + } + return $input; } return utf8_decode($input); @@ -650,8 +733,8 @@ * Adds a query result and returns a handle ID * * @param object $res Query handle + * * @return mixed Handle ID - * @access private */ private function _add_result($res) { @@ -659,7 +742,7 @@ if (PEAR::isError($res)) { $this->db_error = true; $this->db_error_msg = $res->getMessage(); - raise_error(array('code' => 500, 'type' => 'db', + rcube::raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, 'message' => $res->getMessage() . " Query: " . substr(preg_replace('/[\r\n]+\s*/', ' ', $res->userinfo), 0, 512)), @@ -678,17 +761,20 @@ * If no ID is specified, the last resource handle will be returned * * @param number $res_id Handle ID + * * @return mixed Resource handle or false on failure - * @access private */ private function _get_result($res_id = null) { - if ($res_id == null) + if ($res_id == null) { $res_id = $this->last_res_id; + } - if (isset($this->a_query_results[$res_id])) - if (!PEAR::isError($this->a_query_results[$res_id])) + if (isset($this->a_query_results[$res_id])) { + if (!PEAR::isError($this->a_query_results[$res_id])) { return $this->a_query_results[$res_id]; + } + } return false; } @@ -699,54 +785,123 @@ * * @param MDB2 $dbh SQLite database handle * @param string $file_name File path to use for DB creation - * @access private */ - private function _sqlite_create_database($dbh, $file_name) + private function sqlite_create_database($dbh, $file_name) { - if (empty($file_name) || !is_string($file_name)) + if (empty($file_name) || !is_string($file_name)) { return; + } $data = file_get_contents($file_name); - if (strlen($data)) - if (!sqlite_exec($dbh->connection, $data, $error) || MDB2::isError($dbh)) - raise_error(array('code' => 500, 'type' => 'db', + if (strlen($data)) { + if (!sqlite_exec($dbh->connection, $data, $error) || MDB2::isError($dbh)) { + rcube::raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, - 'message' => $error), true, false); + 'message' => $error), true, false); + } + } } /** * Add some proprietary database functions to the current SQLite handle * in order to make it MySQL compatible - * - * @access private */ - private function _sqlite_prepare() + private function sqlite_prepare() { - include_once(INSTALL_PATH . 'program/include/rcube_sqlite.inc'); - - // we emulate via callback some missing MySQL function + // we emulate via callback some missing MySQL functions sqlite_create_function($this->db_handle->connection, - 'from_unixtime', 'rcube_sqlite_from_unixtime'); + 'unix_timestamp', array('rcube_mdb2', 'sqlite_unix_timestamp')); sqlite_create_function($this->db_handle->connection, - 'unix_timestamp', 'rcube_sqlite_unix_timestamp'); - sqlite_create_function($this->db_handle->connection, - 'now', 'rcube_sqlite_now'); - sqlite_create_function($this->db_handle->connection, - 'md5', 'rcube_sqlite_md5'); + 'now', array('rcube_mdb2', 'sqlite_now')); } -} // end class rcube_db - -/* this is our own debug handler for the MDB2 connection */ -function mdb2_debug_handler(&$db, $scope, $message, $context = array()) -{ - if ($scope != 'prepare') { - $debug_output = sprintf('%s(%d): %s;', - $scope, $db->db_index, rtrim($message, ';')); - write_log('sql', $debug_output); + /** + * Debug handler for the MDB2 + */ + function debug_handler(&$db, $scope, $message, $context = array()) + { + if ($scope != 'prepare') { + $debug_output = sprintf('%s(%d): %s;', + $scope, $db->db_index, rtrim($message, ';')); + rcube::write_log('sql', $debug_output); + } } + + + /** + * Return correct name for a specific database table + * + * @param string $table Table name + * + * @return string Translated table name + */ + public function table_name($table) + { + $rcube = rcube::get_instance(); + + // return table name if configured + $config_key = 'db_table_'.$table; + + if ($name = $rcube->config->get($config_key)) { + return $name; + } + + return $table; + } + + + /** + * Return correct name for a specific database sequence + * (used for Postgres only) + * + * @param string $sequence Secuence name + * + * @return string Translated sequence name + */ + public function sequence_name($sequence) + { + $rcube = rcube::get_instance(); + + // return sequence name if configured + $config_key = 'db_sequence_'.$sequence; + + if ($name = $rcube->config->get($config_key)) { + return $name; + } + + return $sequence; + } + + + /** + * Callback for sqlite: unix_timestamp() + */ + public static function sqlite_unix_timestamp($timestamp = '') + { + $timestamp = trim($timestamp); + if (!$timestamp) { + $ret = time(); + } + else if (!preg_match('/^[0-9]+$/s', $timestamp)) { + $ret = strtotime($timestamp); + } + else { + $ret = $timestamp; + } + + return $ret; + } + + + /** + * Callback for sqlite: now() + */ + public static function sqlite_now() + { + return date("Y-m-d H:i:s"); + } + } - -- Gitblit v1.9.1