<?php

	/**
	 * Multi database connection class
	 *
	 */
	class kDBConnection {

		/**
		 * Current database type
		 *
		 * @var string
		 * @access private
		 */
		var $dbType = 'mysql';

		/**
		 * Created connection handle
		 *
		 * @var resource
		 * @access private
		 */
		var $connectionID = null;

		/**
		 * Handle of currenty processed recordset
		 *
		 * @var resource
		 * @access private
		 */
		var $queryID = null;

		/**
		 * DB type specific function mappings
		 *
		 * @var Array
		 * @access private
		 */
		var $metaFunctions = Array();

		/**
		 * Function to handle sql errors
		 *
		 * @var string
		 * @access private
		 */
		var $errorHandler = '';

		/**
		 * Error code
		 *
		 * @var int
		 * @access private
		 */
		var $errorCode = 0;

		/**
		 * Error message
		 *
		 * @var string
		 * @access private
		 */
		var $errorMessage = '';

		/**
		 * Defines if database connection
		 * operations should generate debug
		 * information
		 *
		 * @var bool
		 */
		var $debugMode = false;

		/**
		 * Last query to database
		 *
		 * @var string
		 */
		var $lastQuery = '';

		/**
		 * Initializes connection class with
		 * db type to used in future
		 *
		 * @param string $dbType
		 * @return DBConnection
		 * @access public
		 */
		function kDBConnection($dbType, $errorHandler = '')
		{
			$this->dbType = $dbType;
//			$this->initMetaFunctions();
			if (!$errorHandler) {
				$this->errorHandler = Array(&$this, 'handleError');
			}
			else {
				$this->errorHandler = $errorHandler;
			}
		}

		/**
		 * Set's custom error
		 *
		 * @param int $code
		 * @param string $msg
		 * @access public
		 */
		function setError($code, $msg)
		{
			$this->errorCode = $code;
			$this->errorMessage = $msg;
		}

		/**
		 * Checks if previous query execution
		 * raised an error.
		 *
		 * @return bool
		 * @access public
		 */
		function hasError()
		{
			return !($this->errorCode == 0);
		}

		/**
		 * Caches function specific to requested
		 * db type
		 *
		 * @access private
		 */
		function initMetaFunctions()
		{
			$ret = Array();
			switch ($this->dbType)
			{
				case 'mysql':
					 $ret = Array(); // only define functions, that name differs from "dbType_<meta_name>"

					break;


			}
			$this->metaFunctions = $ret;
		}

		/**
		 * Get's function for specific db type
		 * based on it's meta name
		 *
		 * @param string $name
		 * @return string
		 * @access private
		 */
		function getMetaFunction($name)
		{
			/*if (!isset($this->metaFunctions[$name])) {
				$this->metaFunctions[$name] = $name;
			}*/

			return $this->dbType.'_'.$name;
		}


		/**
		 * Try to connect to database server
		 * using specified parameters and set
		 * database to $db if connection made
		 *
		 * @param string $host
		 * @param string $user
		 * @param string $pass
		 * @param string $db
		 * @access public
		 */
		function Connect($host, $user, $pass, $db, $force_new = false)
		{
			$func = $this->getMetaFunction('connect');
			$this->connectionID = $func($host, $user, $pass, $force_new) or trigger_error("Database connection failed, please check your connection settings", E_USER_ERROR);
			if ($this->connectionID) {
				if (defined('DBG_SQL_MODE')) {
					$this->Query('SET sql_mode = \''.DBG_SQL_MODE.'\'');
				}

				if (defined('SQL_COLLATION') && defined('SQL_CHARSET')) {
					$this->Query('SET NAMES \''.SQL_CHARSET.'\' COLLATE \''.SQL_COLLATION.'\'');
				}

				$this->setDB($db);
				$this->showError();
			}
		}

		function ReConnect($host, $user, $pass, $db, $force_new = false)
		{
			$func = $this->getMetaFunction('close');
			$func($this->connectionID);
			$this->Connect($host, $user, $pass, $db, $force_new);
		}

		/**
		 * Shows error message from previous operation
		 * if it failed
		 *
		 * @access private
		 */
		function showError($sql = '')
		{
			$this->setError(0, ''); // reset error
			if ($this->connectionID) {
				$func = $this->getMetaFunction('errno'); $this->errorCode = $func($this->connectionID);
				if ($this->hasError()) {
					$func = $this->getMetaFunction('error'); $this->errorMessage = $func($this->connectionID);
					if (is_array($this->errorHandler)) {
						$func = $this->errorHandler[1];
						$ret = $this->errorHandler[0]->$func($this->errorCode, $this->errorMessage, $sql);
					}
					else {
						$func = $this->errorHandler;
						$ret = $func($this->errorCode,$this->errorMessage,$sql);
					}
					if (!$ret) exit;
				}
			}
		}

		/**
		 * Default error handler for sql errors
		 *
		 * @param int $code
		 * @param string $msg
		 * @param string $sql
		 * @return bool
		 * @access private
		 */
		function handleError($code, $msg, $sql)
		{
			echo '<b>Processing SQL</b>: '.$sql.'<br>';
			echo '<b>Error ('.$code.'):</b> '.$msg.'<br>';
			return false;
		}

		/**
		 * Set's database name for connection
		 * to $new_name
		 *
		 * @param string $new_name
		 * @return bool
		 * @access public
		 */
		function setDB($new_name)
		{
			if (!$this->connectionID) return false;
			$func = $this->getMetaFunction('select_db');
			return $func($new_name, $this->connectionID);
		}

		/**
		 * Returns first field of first line
		 * of recordset if query ok or false
		 * otherwise
		 *
		 * @param string $sql
		 * @param int $offset
		 * @return string
		 * @access public
		 */
		function GetOne($sql, $offset = 0)
		{
			$row = $this->GetRow($sql, $offset);
			if(!$row) return false;

			return array_shift($row);
		}

		/**
		 * Returns first row of recordset
		 * if query ok, false otherwise
		 *
		 * @param stirng $sql
		 * @param int $offset
		 * @return Array
		 * @access public
		 */
		function GetRow($sql, $offset = 0)
		{
			$sql .= ' '.$this->getLimitClause($offset, 1);
			$ret = $this->Query($sql);
			if(!$ret) return false;

			return array_shift($ret);
		}

		/**
		 * Returns 1st column of recordset as
		 * one-dimensional array or false otherwise
		 * Optional parameter $key_field can be used
		 * to set field name to be used as resulting
		 * array key
		 *
		 * @param string $sql
		 * @param string $key_field
		 * @return Array
		 * @access public
		 */
		function GetCol($sql, $key_field = null)
		{
			$rows = $this->Query($sql);
			if (!$rows) return $rows;

			$i = 0; $row_count = count($rows);
			$ret = Array();
			if (isset($key_field)) {
				while ($i < $row_count) {
					$ret[$rows[$i][$key_field]] = array_shift($rows[$i]);
					$i++;
				}
			}
			else {
				while ($i < $row_count) {
					$ret[] = array_shift($rows[$i]);
					$i++;
				}
			}
			return $ret;
		}

		/**
		 * Queries db with $sql query supplied
		 * and returns rows selected if any, false
		 * otherwise. Optional parameter $key_field
		 * allows to set one of the query fields
		 * value as key in string array.
		 *
		 * @param string $sql
		 * @param string $key_field
		 * @return Array
		 */
		function Query($sql, $key_field = null)
		{
			$this->lastQuery = $sql;
			if ($this->debugMode) return $this->debugQuery($sql,$key_field);
			$query_func = $this->getMetaFunction('query');
			$this->queryID = $query_func($sql,$this->connectionID);
			if (is_resource($this->queryID)) {
				$ret = Array();
				$fetch_func = $this->getMetaFunction('fetch_assoc');
				if (isset($key_field)) {
					while (($row = $fetch_func($this->queryID))) {
						$ret[$row[$key_field]] = $row;
					}
				}
				else {
					while (($row = $fetch_func($this->queryID))) {
						$ret[] = $row;
					}
				}
				$this->Destroy();
				return $ret;
			}
			$this->showError($sql);
			return false;
		}

		function ChangeQuery($sql)
		{
			$this->Query($sql);
			return $this->errorCode == 0 ? true : false;
		}

		function debugQuery($sql, $key_field = null)
		{
			global $debugger;
			$query_func = $this->getMetaFunction('query');

			// set 1st checkpoint: begin
			$profileSQLs = defined('DBG_SQL_PROFILE') && DBG_SQL_PROFILE;
			if ($profileSQLs) {
				$queryID = $debugger->generateID();
				$debugger->profileStart('sql_'.$queryID, $debugger->formatSQL($sql));
			}
			// set 1st checkpoint: end

			$this->queryID = $query_func($sql, $this->connectionID);

			if( is_resource($this->queryID) )
			{
				$ret = Array();
				$fetch_func = $this->getMetaFunction('fetch_assoc');
				if( isset($key_field) )
				{
					while( ($row = $fetch_func($this->queryID)) )
					{
						$ret[$row[$key_field]] = $row;
					}
				}
				else
				{
					while( ($row = $fetch_func($this->queryID)) )
					{
						$ret[] = $row;
					}
				}
				// set 2nd checkpoint: begin
					$first_cell = count($ret) == 1 && count(current($ret)) == 1 ? current(current($ret)) : null;

					$debugger->profileFinish('sql_'.$queryID, null, null, $this->getAffectedRows(), $first_cell);
					$debugger->profilerAddTotal('sql', 'sql_'.$queryID);
				$this->Destroy();
				// set 2nd checkpoint: end
				return $ret;
			}
			else {
				// set 2nd checkpoint: begin
					$debugger->profileFinish('sql_'.$queryID, null, null, $this->getAffectedRows());
					$debugger->profilerAddTotal('sql', 'sql_'.$queryID);
				// set 2nd checkpoint: end
			}
			$this->showError($sql);
			return false;
		}

		/**
		 * Free memory used to hold recordset handle
		 *
		 * @access private
		 */
		function Destroy()
		{
			if($this->queryID)
			{
				$free_func = $this->getMetaFunction('free_result');
				$free_func($this->queryID);
				$this->queryID = null;
			}
		}

		/**
		 * Returns auto increment field value from
		 * insert like operation if any, zero otherwise
		 *
		 * @return int
		 * @access public
		 */
		function getInsertID()
		{
			$func = $this->getMetaFunction('insert_id');
			return $func($this->connectionID);
		}

		/**
		 * Returns row count affected by last query
		 *
		 * @return int
		 * @access public
		 */
		function getAffectedRows()
		{
			$func = $this->getMetaFunction('affected_rows');
			return $func($this->connectionID);
		}

		/**
		 * Returns LIMIT sql clause part for specific db
		 *
		 * @param int $offset
		 * @param int $rows
		 * @return string
		 * @access private
		 */
		function getLimitClause($offset, $rows)
		{
			if(!($rows > 0)) return '';

			switch ($this->dbType) {

				default:
					return 'LIMIT '.$offset.','.$rows;
					break;
			}
		}

		/**
		 * Correctly quotes a string so that all strings are escaped. We prefix and append
		 * to the string single-quotes.
		 * An example is  $db->qstr("Don't bother",magic_quotes_runtime());
		 *
		 * @param s			the string to quote
		 * @param [magic_quotes]	if $s is GET/POST var, set to get_magic_quotes_gpc().
		 *				This undoes the stupidity of magic quotes for GPC.
		 *
		 * @return  quoted string to be sent back to database
		 */
		function qstr($s,$magic_quotes=false)
		{
			$replaceQuote = "\\'";
			if (!$magic_quotes)
			{
				if ($replaceQuote[0] == '\\')
				{
					// only since php 4.0.5
					$s = str_replace(array('\\',"\0"),array('\\\\',"\\\0"),$s);
					//$s = str_replace("\0","\\\0", str_replace('\\','\\\\',$s));
				}
				return  "'".str_replace("'",$replaceQuote,$s)."'";
			}

			// undo magic quotes for "
			$s = str_replace('\\"','"',$s);

			if($replaceQuote == "\\'")  // ' already quoted, no need to change anything
			{
				return "'$s'";
			}
			else  // change \' to '' for sybase/mssql
			{
				$s = str_replace('\\\\','\\',$s);
				return "'".str_replace("\\'",$replaceQuote,$s)."'";
			}
		}

		/**
		 * Returns last error code occured
		 *
		 * @return int
		 */
		function getErrorCode()
		{
			return $this->errorCode;
		}

		/**
		 * Returns last error message
		 *
		 * @return string
		 * @access public
		 */
		function getErrorMsg()
		{
			return $this->errorMessage;
		}

		function doInsert($fields_hash, $table, $type = 'INSERT')
		{
			$fields_sql = '';
			$values_sql = '';
			foreach ($fields_hash as $field_name => $field_value) {
				$fields_sql .= '`'.$field_name.'`,';
				$values_sql .= $this->qstr($field_value).',';
			}

			$fields_sql = preg_replace('/(.*),$/', '\\1', $fields_sql);
			$values_sql = preg_replace('/(.*),$/', '\\1', $values_sql);
			$sql = strtoupper($type).' INTO `'.$table.'` ('.$fields_sql.') VALUES ('.$values_sql.')';

			return $this->ChangeQuery($sql);
		}

		function doUpdate($fields_hash, $table, $key_clause)
		{
			if (!$fields_hash) return true;

			$fields_sql = '';
			foreach ($fields_hash as $field_name => $field_value) {
				$fields_sql .= '`'.$field_name.'` = '.$this->qstr($field_value).',';
			}

			$fields_sql = preg_replace('/(.*),$/', '\\1', $fields_sql);

			$sql = 'UPDATE `'.$table.'` SET '.$fields_sql.' WHERE '.$key_clause;

			return $this->ChangeQuery($sql);
		}

		/**
		 * Allows to detect table's presense in database
		 *
		 * @param string $table_name
		 * @return bool
		 */
		function TableFound($table_name)
		{
			static $table_found = Array();

			if (!preg_match('/^'.preg_quote(TABLE_PREFIX, '/').'(.*)/', $table_name)) {
				$table_name = TABLE_PREFIX.$table_name;
			}

			if (!isset($table_found[$table_name])) {
				$table_found[$table_name] = $this->Query('SHOW TABLES LIKE "'.$table_name.'"');
			}

			return $table_found[$table_name];
		}
	}
?>