| <?php
/**
 * @author Indrek Altpere
 * @copyright Indrek Altpere
 * @uses Mysql package released by me
 * @see ErrorManager for convenient error logging
 *
 * Provides means of more convenient mysql usage by making it possible to call the functions as static functions and thus removing the need of passing the mysql connection variable around.
 * Static wrapper class for DynMysql class, can contain only connection to one database, for using multiple databases simultaneously, use DynMysql class
 *
 */
class Mysql {
	/**
	 * DynMysql instance
	 *
	 * @var DynMysqlBase
	 */
	private static $mysql;
	private static $inited = false;
	const Version = 1.3;
	/**
	 * Disables instantiating of this class
	 *
	 */
	private function __construct() {
	}
	/**
	 * Returns the singleton Mysql instance.
	 * @return DynMysqlBase
	 */
	public static function instance() {
		return self::$mysql;
	}
	/**
	 * Returns mysql escaped string
	 *
	 * @param string $str String to escape
	 * @param bool $likemode If string is to be escaped for LIKE "xx" query, where _ and % need to be escaped too
	 * @return string Escaped string
	 */
	public static function EscapeString($str, $likemode = false) {
		return self::$mysql->EscapeString($str, $likemode);
	}
	/**
	 * Initializes the static Mysql class variables
	 *
	 * @param string $db_host Hostname
	 * @param string $db_user Username
	 * @param string $db_pass Password
	 * @param string $db_name Database name
	 * @param boolean $autoconnect Whether to initialize connection right away or when first query is made
	 * @param boolean $persistent Whether to use persisten connection
	 */
	public static function Init($db_host, $db_user, $db_pass, $db_name, $autoconnect = false, $persistent = false, $defaultcharset = null, $compression = false) {
		if (self::$inited) {
			return;
		}
		//if possible, try to use mysqli, newer PHP will be deprecating older mysql extension
		if (function_exists('mysqli_connect')) {
			self::$mysql = new DynMysqli($db_host, $db_user, $db_pass, $db_name, $autoconnect, $persistent, $defaultcharset, $compression);
		} else
			self::$mysql = new DynMysql($db_host, $db_user, $db_pass, $db_name, $autoconnect, $persistent, $defaultcharset, $compression);
		self::$inited = true;
	}
	public static function GetLastError() {
		return self::$mysql->GetLastError();
	}
	/**
	 * Performs query on database
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @return mysql_result Mysql result
	 */
	public static function &Query($cmd) {
		return self::$mysql->Query($cmd);
	}
	public static function SetSlownessTreshold($treshold = 0) {
		return self::$mysql->SetSlownessThreshold($treshold);
	}
	public static function SetSlownessThreshold($treshold = 0) {
		return self::$mysql->SetSlownessThreshold($treshold);
	}
	/**
	 * Returns how many rows were found in last query
	 * Note: last query must include SQL_CALC_FOUND_ROWS for thist to work as meant to
	 * It is meant to take away the 2 query step where you first select the count of rows according to WHERE statement (to know the total corresponding rows),
	 * after which you select the actual rows itself with limit statement to display data on multiple pages for example.
	 * SQL_CALC_FOUND_ROWS tells mysql to remember the count of total found rows even if you used LIMIT statement to get only partial result of all matches.
	 *
	 * @return int Count of found rows
	 */
	public static function FoundRows() {
		return self::$mysql->FoundRows();
	}
	/**
	 * Returns single mysql result row as unassociated array of selected field values (array keys are integers, not fieldnames) on success, false otherwise
	 * array('val1', 'val2')
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @return array|boolean Array of field values or false if query was unsuccessful
	 */
	public static function &GetRow($cmd) {
		return self::$mysql->GetRow($cmd);
	}
	/**
	 * Returns multiple mysql result rows as array of unassociated arrays of selected field values (array keys are integers, not fieldnames), empty array if unsuccessful
	 * array(array('val1', 'val2'), array('val3', 'val4'))
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @param boolean $bundle Whether to bundle first values of result rows into one single level array
	 * @return array Array of results
	 */
	public static function &GetRows($cmd, $bundle = false) {
		return self::$mysql->GetRows($cmd, $bundle);
	}
	/**
	 * Returns multiple mysql result rows as array of associated arrays of selected field values (array keys are fieldnames), empty array in unsuccessful
	 * array(array('name1' => 'val1', 'name2' => 'val2'), array('name1' => 'val3', 'name2' => 'val4'))
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @param boolean $assignid Whether to return arrays so that the id field values are set to be the keys of the arrays ( idvalue => array(fieldname=>fieldvalue) )
	 * @param boolean $bundle Whether to bundle all subarrays into one single array using the first subarray value (good to select id fields and instead of looping with nested foreaches or using $row['id'], just get the values as one array)
	 * @return array Array of results
	 */
	public static function &GetArrays($cmd, $assignid = false, $bundle = false, $idfield = 'id') {
		return self::$mysql->GetArrays($cmd, $assignid, $bundle, $idfield);
	}
	/**
	 * Returns single mysql result rows as associated array of selected field values (array keys are fieldnames), false in unsuccessful
	 * array('name1' => 'val1', 'name2' => 'val2')
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @return array|boolean Array of fielname => fieldvalue mappings or false if unsuccessful
	 */
	public static function &GetArray($cmd) {
		return self::$mysql->GetArray($cmd);
	}
	/**
	 * Returns the id of the last row inserted into database
	 * @return int Id of last inserted row
	 */
	public static function &InsertId() {
		return self::$mysql->InsertId();
	}
	/**
	 * Deprecated: returns scalar value of query (first cell of first row)
	 * @param string $cmd Mysql query to make
	 * @return string
	 * @deprecated
	 */
	public static function &GetSingleRowField($cmd) {
		return self::$mysql->GetScalar($cmd);
	}
	/**
	 * Returns first column value of first selected row
	 * @param string $cmd Mysql query to make
	 * @return string
	 */
	public static function &GetScalar($cmd) {
		return self::$mysql->GetScalar($cmd);
	}
	/**
	 * Sets the Mysql class debug mode (in debug mode, queryes and their related values are stored and can be viewed by calling ToString method)
	 * @param boolean $debug
	 * @param boolean $debugtrace
	 */
	public static function SetDebug($debug = false, $debugtrace = false) {
		self::$mysql->SetDebug($debug, $debugtrace);
	}
	/**
	 * Returns data about executed mysql queries in string form, to get more detailed data about each queries(spent time, affected rows etc), use SetDebug(true) before making any queries
	 * @return string
	 */
	public static function ToString() {
		return self::$mysql->ToString();
	}
	/**
	 * Starts transaction
	 * @return boolean If transaction was started successfully
	 */
	public static function TransactionBegin() {
		return self::$mysql->TransactionBegin();
	}
	/**
	 * Ends/commits transaction
	 * @return boolean If commiting was successful
	 */
	public static function TransactionEnd() {
		return self::$mysql->TransactionEnd();
	}
	/**
	 * Rolls back current transaction
	 * @return boolean If rolling back was successful
	 */
	public static function TransactionRollback() {
		return self::$mysql->TransactionRollback();
	}
	/**
	 * Retrieves iterator class for result
	 * @param string $query
	 * @return MysqlIterator
	 */
	public static function &GetIterator($query) {
		return self::$mysql->GetIterator($query);
	}
	/**
	 * Gets full column data description for wanted table as associative array with keys:
	 * Field, Type, Collation, Null, Key, Default, Extra, Privileges, Comment
	 * @param string $tablename Name of the table for what to get the column data
	 * @return array Array of
	 */
	public static function GetColumnDataForTable($tablename) {
		return self::$mysql->GetColumnDataForTable($tablename);
	}
	/**
	 * Gets the results of table expain query as associative array with keys:
	 * Field, Type, Null, Key, Default, Extra
	 * @param string $tablename
	 */
	public static function GetExplainTable($tablename) {
		return self::$mysql->GetExplainTable($tablename);
	}
	/**
	 * Builds Order by statement from passed in array(fieldname => order)
	 * @param array $fieldorderarr
	 * @return string
	 */
	public static function &BuildOrderByStatement($fieldorderarr) {
		return self::$mysql->BuildOrderByStatement($fieldorderarr);
	}
	/**
	 * Builds Limit statement from passed in variables
	 * @param int $start Start of limit array
	 * @param int $count Count of rows to select
	 * @return string
	 */
	public static function &BuildLimitStatement($start, $count) {
		return self::$mysql->BuildLimitStatement($start, $count);
	}
	/**
	 * Builds Set statement from passed in array
	 * @param array $array
	 * @return string Set statement
	 */
	public static function BuildSetStatement(&$array) {
		return self::$mysql->BuildSetStatement($array);
	}
	/**
	 * Builds Insert statement for given table using given fields and values
	 * @param string $tablename Name of the table
	 * @param array $array Field => Value array
	 * @return string Statement for inserting data into table
	 */
	public static function BuildInsertStatement($tablename, &$array) {
		return self::$mysql->BuildInsertStatement($tablename, $array);
	}
	/**
	 * Builds Update statement for given table using given fields and values
	 * @param string $tablename Name of the table
	 * @param array $array Field => Value array
	 * @param int $id Id of the row to update
	 * @return string Statement for updating a row data in table
	 */
	public static function BuildUpdateStatement($tablename, &$array, $id, $idfield = 'id') {
		return self::$mysql->BuildUpdateStatement($tablename, $array, $id, $idfield);
	}
	/**
	 * Builds Delete statement for given table using given id
	 * @param string $tablename Name of the table
	 * @param int $id Id of the row to delete
	 * @return string Statement for deleting a row from table
	 */
	public static function BuildDeleteStatement($tablename, $id) {
		return self::$mysql->BuildDeleteStatement($tablename, $id);
	}
	/**
	 * Truncates table
	 * @param string $tablename Name of the table
	 * @return boolean If truncating was successful
	 */
	public static function TruncateTable($tablename) {
		return self::$mysql->TruncateTable($tablename);
	}
	/**
	 * Retrieves table list from database
	 * @return array Array of table names in current database
	 */
	public static function &GetTables() {
		return self::$mysql->GetTables();
	}
	/**
	 * Retrieves list of fields from given table
	 * @param string $tablename
	 * @return array
	 */
	public static function &GetTableFields($tablename) {
		return self::$mysql->GetTableFields($tablename);
	}
	/**
	 * Returns time spent on last query
	 * @return float
	 */
	public static function TimeSpent() {
		return self::$mysql->TimeSpent();
	}
	/**
	 * Returns time spent on all queries together
	 * @return float
	 */
	public static function TimeSpentTotal() {
		return self::$mysql->TimeSpentTotal();
	}
	/**
	 * Returns how many rows were selected in last queriy
	 * @return int
	 */
	public static function SelectedRows() {
		return self::$mysql->SelectedRows();
	}
	/**
	 * Returns how many total rows were selected in all queries together
	 * @return int
	 */
	public static function SelectedRowsTotal() {
		return self::$mysql->SelectedRowsTotal();
	}
	/**
	 * Returns how many rows were affected by last query
	 * @return int
	 */
	public static function AffectedRows() {
		return self::$mysql->AffectedRows();
	}
	/**
	 * Returns how many total rows were affected in all queries together
	 * @return int
	 */
	public static function AffectedRowsTotal() {
		return self::$mysql->AffectedRowsTotal();
	}
	/**
	 * Returns how many queries were run
	 * @return int
	 */
	public static function TotalQueries() {
		return self::$mysql->TotalQueries();
	}
	/**
	 * Returns if there is a transaction active currently
	 * @return bool
	 */
	public static function InTransaction() {
		return self::$mysql->InTransaction();
	}
	/**
	 * Tries to use another database using the current opened connection (current user must have rights to the other database)
	 * @param string $db_name
	 * @return bool
	 */
	public static function UseDatabase($db_name) {
		return self::$mysql->UseDatabase($db_name);
	}
	public static function Reconnect() {
		return self::$mysql->Reconnect();
	}
	public static function Connected() {
		return self::$mysql->Connected();
	}
	public static function SetTimezone($timezone) {
		return self::$mysql->SetTimezone($timezone);
	}
	public static function SetCharset($set = 'utf8') {
		return self::$mysql->SetCharset($set);
	}
	public static function SetDatabaseHandle($db) {
		return self::$mysql->SetDatabaseHandle($db);
	}
	public static function Close() {
		return self::$mysql->Close();
	}
	/**
	 * Sets if mysql errors are converted to trigger_error for errorhandler
	 * @param bool $new
	 * @return bool
	 */
	public static function SetTriggerError($new = false) {
		return self::$mysql->SetTriggerError($new);
	}
}
/**
 * Class for mysql stuff, each instance can be connected to any database
 *
 * Supports delayed connecting: if you instantiate class, it does not bring up the mysql connection unless autoconnect is set to true
 * If autoconnect was set to false, first query made triggers the connection creation
 * Meaning if site does not need mysql queries to be done, the mysql connection is not brought up and therefore page loading takes less time
 *
 */
final class DynMysql extends DynMysqlBase {
	public function db_affected_rows() {
		return mysql_affected_rows($this->db);
	}
	public function db_close() {
		return mysql_close($this->db);
	}
	public function db_connect($host, $user, $pass, $new_link = false) {
		//use special flags if needed
		$flags = $this->compression ? MYSQL_CLIENT_COMPRESS : 0;
		return mysql_connect($host, $user, $pass, $new_link, $flags);
	}
	public function db_pconnect($host, $user, $pass, $new_link = false) {
		if ($new_link)
			return $this->db_connect($host, $user, $pass, $new_link);
		//use special flags if needed
		$flags = $this->compression ? MYSQL_CLIENT_COMPRESS : 0;
		return mysql_pconnect($host, $user, $pass, $flags);
	}
	public function db_errno() {
		return mysql_errno($this->db);
	}
	public function db_error() {
		return mysql_error($this->db);
	}
	public function db_escape_string($str) {
		return mysql_real_escape_string($str, $this->db);
	}
	public function db_fetch_assoc($res) {
		return mysql_fetch_assoc($res);
	}
	public function db_fetch_row($res) {
		return mysql_fetch_row($res);
	}
	public function db_free_result($res) {
		return mysql_free_result($res);
	}
	public function db_insert_id() {
		return mysql_insert_id($this->db);
	}
	public function db_num_rows($res) {
		return mysql_num_rows($res);
	}
	public function db_query($sql) {
		return mysql_query($sql, $this->db);
	}
	public function db_select_db($dbname) {
		return mysql_select_db($dbname, $this->db);
	}
	public function db_set_charset($set) {
		if (!function_exists('mysql_set_charset'))
			return false;
		return mysql_set_charset($set, $this->db);
	}
	public function db_warning_count() {
		//old mysql does not have warning_count helper, use SELECT when in debug mode
		if ($this->debug) {
			$res = $this->db_query('SELECT @@warning_count');
			if ($res) {
				$row = $this->db_fetch_row($res);
				$this->db_free_result($res);
				return (int) $row[0];
			}
		} else
			return 0;
	}
	public function db_get_server_version() {
		return 0;
	}
}
/**
 * Class for mysql stuff, each instance can be connected to any database
 *
 * Supports delayed connecting: if you instantiate class, it does not bring up the mysql connection unless autoconnect is set to true
 * If autoconnect was set to false, first query made triggers the connection creation
 * Meaning if site does not need mysql queries to be done, the mysql connection is not brought up and therefore page loading takes less time
 *
 */
final class DynMysqli extends DynMysqlBase {
	public function db_affected_rows() {
		return mysqli_affected_rows($this->db);
	}
	public function db_close() {
		return mysqli_close($this->db);
	}
	public function db_connect($host, $user, $pass, $new_link = false) {
		//use regular initialization
		if (!$this->compression)
			return mysqli_connect($host, $user, $pass);
		//to pass compression flags, we need some special logic
		$mysqli = mysqli_init();
		$flags = MYSQLI_CLIENT_COMPRESS;
		if (mysqli_real_connect($mysqli, $host, $user, $pass, null, null, null, $flags))
			return $mysqli;
		mysqli_close($mysqli);
		return false;
	}
	public function db_pconnect($host, $user, $pass, $new_link = false) {
		//no pconnect for mysqli, reuse connect
		return $this->db_connect($host, $user, $pass, $new_link);
	}
	public function db_errno() {
		return mysqli_errno($this->db);
	}
	public function db_error() {
		return mysqli_error($this->db);
	}
	public function db_escape_string($str) {
		return mysqli_real_escape_string($this->db, $str);
	}
	public function db_fetch_assoc($res) {
		return mysqli_fetch_assoc($res);
	}
	public function db_fetch_row($res) {
		return mysqli_fetch_row($res);
	}
	public function db_free_result($res) {
		return mysqli_free_result($res);
	}
	public function db_insert_id() {
		return mysqli_insert_id($this->db);
	}
	public function db_num_rows($res) {
		return mysqli_num_rows($res);
	}
	public function db_query($sql) {
		return mysqli_query($this->db, $sql);
	}
	public function db_select_db($dbname) {
		return mysqli_select_db($this->db, $dbname);
	}
	public function db_set_charset($set) {
		return mysqli_set_charset($this->db, $set);
	}
	public function db_warning_count() {
		return mysqli_warning_count($this->db);
	}
	public function db_get_server_version() {
		return mysqli_get_server_version($this->db);
	}
}
/**
 * Class for mysql stuff, each instance can be connected to any database
 *
 * Supports delayed connecting: if you instantiate class, it does not bring up the mysql connection unless autoconnect is set to true
 * If autoconnect was set to false, first query made triggers the connection creation
 * Meaning if site does not need mysql queries to be done, the mysql connection is not brought up and therefore page loading takes less time
 *
 */
abstract class DynMysqlBase {
	private $res = null;
	private $queries = 0;
	protected $db = null; //to allow access from db_* functions
	private $time_spent_total = 0;
	private $affected_rows_total = 0;
	private $selected_rows_total = 0;
	private $time_spent = 0;
	private $affected_rows = 0;
	private $selected_rows = 0;
	private $conndata = array('db_host' => '', 'db_user' => '', 'db_pass' => '', 'db_name' => '');
	private $defaultcharset = null;
	private $persistent = false;
	private $queryarr = array();
	protected $debug = false; //to allow access from db_* functions (for mysql warnings_count)
	protected $compression = false; //to allow access from db_connect function
	private $debugtrace = false;
	private $in_transaction = false;
	private $trigger_error = true;
	/**
	 * Initializes the DynMysql class variables
	 *
	 * @param string $db_host Hostname
	 * @param string $db_user Username
	 * @param string $db_pass Password
	 * @param string $db_name Database name
	 * @param boolean $autoconnect Whether to initialize connection right away or when first query is made
	 * @param boolean $persistent Whether to use persisten connection or not
	 */
	public function __construct($db_host, $db_user, $db_pass, $db_name, $autoconnect = false, $persistent = false, $defaultcharset = null, $compression = false) {
		$this->conndata = array('db_host' => $db_host, 'db_user' => $db_user, 'db_pass' => $db_pass, 'db_name' => $db_name);
		$this->persistent = $persistent;
		$this->defaultcharset = $defaultcharset;
		$this->compression = !!$compression;
		if ($autoconnect) {
			$this->Connect();
		}
	}
	/**
	 * Destructor, closes open connections
	 */
	public function __destruct() {
		$this->Close();
	}
	abstract function db_close();
	abstract function db_escape_string($str);
	abstract function db_connect($host, $user, $pass, $new_link = false);
	abstract function db_pconnect($host, $user, $pass, $new_link = false);
	abstract function db_select_db($dbname);
	abstract function db_error();
	abstract function db_errno();
	abstract function db_query($sql);
	abstract function db_affected_rows();
	abstract function db_num_rows($res);
	abstract function db_fetch_assoc($res);
	abstract function db_fetch_row($res);
	abstract function db_free_result($res);
	abstract function db_set_charset($set);
	abstract function db_insert_id();
	abstract function db_warning_count();
	abstract function db_get_server_version();
	/**
	 * Closes existing connection
	 */
	public function Close() {
		if ($this->db) {
			$this->db_close();
			$this->db = null;
		}
	}
	/**
	 * Returns mysql escaped string
	 *
	 * @param string $str String to escape
	 * @param bool $likemode If string is to be escaped for LIKE "xx" query, where _ and % need to be escaped too
	 * @return string Escaped string
	 */
	public function EscapeString($str, $likemode = false) {
		//init db conn if needed
		if (is_null($this->db)) {
			$this->Connect();
			if (is_null($this->db)) {
				trigger_error('Mysql error: No connection to database!?!?', E_USER_ERROR);
				return null;
			}
		}
		if (!$likemode)
			return $this->db_escape_string($str);
		return str_replace(array('_', '%'), array('\_', '\%'), $this->db_escape_string($str));
	}
	private $failedconnections = 0;
	private function Connect() {
		if ($this->db) {
			return true;
		}
		if ($this->failedconnections > 2)
			return false;
		$fname = 'db_' . ($this->persistent ? 'p' : '') . 'connect';
		$conn = &$this->conndata;
		$this->db = $this->$fname($conn['db_host'], $conn['db_user'], $conn['db_pass']);
		if (!$this->db) {
			$this->db = null;
			$this->failedconnections++;
			return false;
		}
		if (!$this->db_select_db($conn['db_name'])) {
			$this->failedconnections++;
			$this->db_close();
			$this->db = null;
			return false;
		}
		if (!is_null($this->defaultcharset)) {
			$this->SetCharset($this->defaultcharset);
		}
		return true;
	}
	public function SetDatabaseHandle($db) {
		$this->db = $db;
	}
	public function Reconnect() {
		if ($this->failedconnections > 2)
			return false;
		$fname = 'db_' . ($this->persistent ? 'p' : '') . 'connect';
		$conn = &$this->conndata;
		$this->db = $this->$fname($conn['db_host'], $conn['db_user'], $conn['db_pass'], true);
		if (!$this->db) {
			$this->db = null;
			$this->failedconnections++;
			return false;
		}
		if (!$this->db_select_db($conn['db_name'])) {
			$this->db_close();
			$this->db = null;
			$this->failedconnections++;
			return false;
		}
		if (!is_null($this->defaultcharset)) {
			$this->SetCharset($this->defaultcharset);
		}
	}
	public function Connected() {
		return !!$this->db;
	}
	public function UseDatabase($db_name) {
		if (!$this->db) {
			trigger_error('Cannot select database when not connected to mysql server!');
			return false;
		}
		if (!$this->db_select_db($db_name)) {
			trigger_error('Could not select database: ' . $this->db_error());
			return false;
		}
		return true;
	}
	private $lastError = false;
	public function GetLastError() {
		return $this->lastError;
	}
	/**
	 * Performs query on database
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @return mysql_result Mysql result
	 */
	public function &Query($cmd, $addDebugData = true) {
		$q_start = microtime(true);
		if ($this->db === null) {
			if (!$this->Connect())
				return null;
		}
		$this->queries++;
		$this->res = $this->db_query($cmd);
		$errno = $this->db_errno();
		if ($errno == 2006 || $errno == 2013) {//server has gone away || lost connection during query
			$this->db = null;
			$this->Reconnect();
			//run query again after reconnect and also fetch errno again
			$this->res = $this->db_query($cmd);
			$errno = $this->db_errno();
		}
		$err = $this->db_error();
		if ($errno) {
			if ($this->trigger_error)
				trigger_error('Mysql error ' . $errno . ': ' . $err . " ($cmd)", E_USER_ERROR);
			$this->lastError = $errno . ': ' . $err;
		} else
			$this->lastError = false;
		$this->affected_rows = @$this->db_affected_rows();
		$this->affected_rows_total += $this->affected_rows;
		$this->selected_rows = $this->res && $this->res !== true /* UPDATE queries return true, not result */ ? @$this->db_num_rows($this->res) : 0;
		$this->selected_rows_total += $this->selected_rows;
		$q_end = microtime(true);
		if ($this->slowness_threshold && $q_end - $q_start >= $this->slowness_threshold) {
			trigger_error('Mysql slowness warning ' . number_format($q_end - $q_start, 4) . 's: ' . $cmd, E_USER_WARNING);
		}
		if ($addDebugData) {
			$this->time_spent = $q_end - $q_start;
			$this->time_spent_total += $this->time_spent;
			$this->AddDebugData($cmd);
		}
		return $this->res;
	}
	private $slowness_threshold = 0;
	public function SetSlownessThreshold($threshold = 0) {
		$this->slowness_threshold = max(0.0, (float) $threshold);
	}
	/**
	 * Returns how many rows were found in last query
	 * Note: last query must include SQL_CALC_FOUND_ROWS for thist to work as meant to
	 * It is meant to take away the 2 query step where you first select the count of rows according to WHERE statement (to know the total corresponding rows),
	 * after which you select the actual rows itself with limit statement to display data on multiple pages for example.
	 * SQL_CALC_FOUND_ROWS tells mysql to remember the count of total found rows even if you used LIMIT statement to get only partial result of all matches.
	 *
	 * @return int Count of found rows
	 */
	public function FoundRows() {
		$buf = $this->GetRow('SELECT FOUND_ROWS()');
		return intval($buf[0]);
	}
	/**
	 * Returns single mysql result row as unassociated array of selected field values (array keys are integers, not fieldnames) on success, false otherwise
	 * array('val1', 'val2')
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @return array|boolean Array of field values or false if query was unsuccessful
	 */
	public function &GetRow($cmd) {
		$q_start = microtime(true);
		if ($this->Query($cmd, false)) {
			$buf = $this->db_fetch_row($this->res);
		} else
			$buf = false;
		$q_end = microtime(true);
		$this->time_spent = $q_end - $q_start;
		$this->time_spent_total += $this->time_spent;
		$this->AddDebugData($cmd);
		$this->FreeResult();
		return $buf;
	}
	/**
	 * Returns multiple mysql result rows as array of unassociated arrays of selected field values (array keys are integers, not fieldnames), empty array if unsuccessful
	 * array(array('val1', 'val2'), array('val3', 'val4'))
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @param boolean $bundle Whether to bundle first values of result rows into one single level array
	 * @return array Array of results
	 */
	public function &GetRows($cmd, $bundle = false) {
		$q_start = microtime(true);
		$m = array();
		if ($this->Query($cmd, false)) {
			while (($t = $this->db_fetch_row($this->res))) {
				if ($bundle) {
					$m[] = reset($t);
				} else {
					$m[] = $t;
				}
			}
		}
		$q_end = microtime(true);
		$this->time_spent = $q_end - $q_start;
		$this->time_spent_total += $this->time_spent;
		$this->AddDebugData($cmd);
		$this->FreeResult();
		return $m;
	}
	/**
	 * Returns multiple mysql result rows as array of associated arrays of selected field values (array keys are fieldnames), empty array in unsuccessful
	 * array(array('name1' => 'val1', 'name2' => 'val2'), array('name1' => 'val3', 'name2' => 'val4'))
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @param boolean $assignid Whether to return arrays so that the id field values are set to be the keys of the arrays ( idvalue => array(fieldname=>fieldvalue) )
	 * @param boolean $bundle Whether to bundle all subarrays into one single array using the first subarray value (good to select id fields and instead of looping with nested foreaches or using $row['id'], just get the values as one array)
	 * @return array Array of results
	 */
	public function &GetArrays($cmd, $assignid = false, $bundle = false, $idfield = 'id') {
		$q_start = microtime(true);
		$m = array();
		if ($this->Query($cmd, false)) {
			while (($t = $this->db_fetch_assoc($this->res))) {
				if ($assignid) {
					$id = $t[$idfield];
					if ($bundle)
						unset($t[$idfield]);
					$m[$id] = $bundle ? reset($t) : $t;
				} elseif ($bundle) {
					$m[] = reset($t);
				} else {
					$m[] = $t;
				}
			}
		}
		$q_end = microtime(true);
		$this->time_spent = $q_end - $q_start;
		$this->time_spent_total += $this->time_spent;
		$this->AddDebugData($cmd);
		$this->FreeResult();
		return $m;
	}
	/**
	 * Returns single mysql result rows as associated array of selected field values (array keys are fieldnames), false in unsuccessful
	 * array('name1' => 'val1', 'name2' => 'val2')
	 *
	 * Note: Use SQL_CALC_FOUND_ROWS immediately after SELECT (example: SELECT SQL_CALC_FOUND_ROWS id FROM sometable WHERE somefield="2" LIMIT 100,10)
	 * to be able to get the total count of rows that matched the WHERE statement (300, 400, 1000 or whatever count rows match) later by calling FoundRows()
	 *
	 * @param string $cmd Mysql query to make
	 * @return array|boolean Array of fielname => fieldvalue mappings or false if unsuccessful
	 */
	public function &GetArray($cmd) {
		$q_start = microtime(true);
		if ($this->Query($cmd, false)) {
			$buf = $this->db_fetch_assoc($this->res);
		} else
			$buf = false;
		$q_end = microtime(true);
		$this->time_spent = $q_end - $q_start;
		$this->time_spent_total += $this->time_spent;
		$this->AddDebugData($cmd);
		$this->FreeResult();
		return $buf;
	}
	public function &GetSingleRowField($cmd) {
		return $this->GetScalar($cmd);
	}
	/**
	 * Returns first column value of first selected row
	 * @param string $cmd Mysql query to make
	 * @return string
	 */
	public function &GetScalar($cmd) {
		$q_start = microtime(true);
		if ($this->Query($cmd, false)) {
			$buf = $this->db_fetch_row($this->res);
		} else
			$buf = false;
		$q_end = microtime(true);
		$this->time_spent = $q_end - $q_start;
		$this->time_spent_total += $this->time_spent;
		$this->AddDebugData($cmd);
		$this->FreeResult();
		$ret = false;
		if (is_array($buf)) {
			$ret = reset($buf);
		}
		return $ret;
	}
	/**
	 * Returns the id of the last row inserted into database
	 * @return int Id of last inserted row
	 */
	public function &InsertId() {
		$id = $this->db_insert_id();
		$errno = $this->db_errno();
		if ($errno) {
			$err = $this->db_error();
			trigger_error('Mysql error ' . $errno . ': ' . $err . " (getting inserted id)", E_USER_ERROR);
		}
		return $id;
	}
	/**
	 * Starts transaction
	 * @return boolean If transaction was started successfully
	 */
	public function TransactionBegin() {
		if ($this->in_transaction) {
			return false;
		}
		$res = $this->Query('START TRANSACTION');
		if (!$res) {
			return false;
		}
		$this->in_transaction = true;
		return true;
	}
	/**
	 * Ends/commits transaction
	 * @return boolean If commiting was successful
	 */
	public function TransactionEnd() {
		if (!$this->in_transaction) {
			return false;
		}
		$this->in_transaction = false;
		$res = $this->Query('COMMIT');
		if (!$res) {
			return false;
		}
		return true;
	}
	/**
	 * Rolls back current transaction
	 * @return boolean If rolling back was successful
	 */
	public function TransactionRollback() {
		if (!$this->in_transaction) {
			return false;
		}
		$this->in_transaction = false;
		$res = $this->Query('ROLLBACK');
		if (!$res) {
			return false;
		}
		return true;
	}
	/**
	 * Gets full column data description for wanted table as associative array with keys: Field, Type, Collation, Null, Key, Default, Extra, Privileges, Comment
	 * @param string $tablename Name of the table for what to get the column data
	 * @return array Array of
	 */
	public function GetColumnDataForTable($tablename) {
		return $this->GetArrays('SHOW FULL COLUMNS FROM `' . $this->EscapeString($tablename) . '`');
	}
	/**
	 * Gets the results of table expain query as associative array with keys: Field, Type, Null, Key, Default, Extra
	 * @param string $tablename
	 */
	public function GetExplainTable($tablename) {
		return $this->GetArrays('EXPLAIN `' . $this->EscapeString($tablename) . '`');
	}
	/**
	 * Builds Order by statement from passed in array(fieldname => order)
	 * @param array $fieldorderarr
	 * @return string
	 */
	public function &BuildOrderByStatement($fieldorderarr) {
		if (!is_array($fieldorderarr) || !count($fieldorderarr)) {
			return '';
		}
		$allowedorders = array('ASC', 'DESC');
		$newarr = array();
		foreach ($fieldorderarr as $field => $order) {
			$order = strtoupper($order);
			if (!in_array($order, $allowedorders, true))
				$order = 'ASC';
			$newarr[] = '`' . $this->EscapeString($field) . '` ' . $order;
		}
		$str = 'ORDER BY ' . join(',', $newarr);
		return $str;
	}
	/**
	 * Builds Limit statement from passed in variables
	 * @param int $start Start of limit array
	 * @param int $count Count of rows to select
	 * @return string
	 */
	public function &BuildLimitStatement($start, $count) {
		$start = max(0, intval($start));
		$count = abs(intval($count));
		$str = 'LIMIT ' . $start . ',' . $count;
		return $str;
	}
	/**
	 * Builds Set statement from passed in array
	 * @param array $array
	 * @return string Set statement
	 */
	public function &BuildSetStatement(&$array) {
		if (!count($array))
			return '';
		$str = 'SET ';
		$strarr = array();
		foreach ($array as $k => &$v) {
			//if field is null, set database value to NULL also, otherwise escape it and put between "" since mysql does its own conversion anyways ("2" => 2)
			$strarr[] = '`' . $this->EscapeString($k) . '`=' . (is_null($v) ? 'NULL' : '"' . $this->EscapeString($v) . '"');
		}
		$str .= join(',', $strarr);
		return $str;
	}
	/**
	 * Builds Insert statement for given table using given fields and values
	 * @param string $tablename Name of the table
	 * @param array $array Field => Value array
	 * @return string Statement for inserting data into table
	 */
	public function BuildInsertStatement($tablename, &$array) {
		return 'INSERT INTO `' . $this->EscapeString($tablename) . '` ' . $this->BuildSetStatement($array);
	}
	/**
	 * Builds Update statement for given table using given fields and values
	 *
	 * @param string $tablename Name of the table
	 * @param array $array Field => Value array
	 * @param int $id Id of the row to update
	 * @return string Statement for updating a row data in table
	 */
	public function BuildUpdateStatement($tablename, &$array, $id, $idfield = 'id') {
		return 'UPDATE ' . $this->EscapeString($tablename) . ' ' . $this->BuildSetStatement($array) . ' WHERE `' . $this->EscapeString($idfield) . '`=' . intval($id);
	}
	/**
	 * Builds Delete statement for given table using given id
	 * @param string $tablename Name of the table
	 * @param int $id Id of the row to delete
	 * @return string Statement for deleting a row from table
	 */
	public function BuildDeleteStatement($tablename, $id) {
		return 'DELETE FROM `' . $this->EscapeString($tablename) . '` WHERE `id`=' . intval($id);
	}
	/**
	 * Truncates table
	 * @param string $tablename Name of the table
	 * @return boolean If truncating was successful
	 */
	public function TruncateTable($tablename) {
		$res = $this->Query('TRUNCATE TABLE `' . $this->EscapeString($tablename) . '`');
		return $res ? true : false;
	}
	/**
	 * Retrieves table list from database
	 * @return array Array of table names in current database
	 */
	public function &GetTables() {
		return $this->GetRows('SHOW TABLES', true);
	}
	/**
	 * Retrieves list of fields from given table
	 * @param string $tablename
	 * @return array
	 */
	public function &GetTableFields($tablename) {
		return $this->GetRows('EXPLAIN `' . $this->EscapeString($tablename) . '`', true);
	}
	/**
	 * Retrieves iterator class for result
	 * @param string $query
	 * @return MysqlIterator
	 */
	public function GetIterator($query) {
		return new MysqlIterator($query);
	}
	/**
	 * Returns time spent on last query
	 * @return float
	 */
	public function TimeSpent() {
		return $this->time_spent;
	}
	/**
	 * Returns time spent on all queries together
	 * @return float
	 */
	public function TimeSpentTotal() {
		return $this->time_spent_total;
	}
	/**
	 * Returns how many rows were selected in last queriy
	 * @return int
	 */
	public function SelectedRows() {
		return $this->selected_rows;
	}
	/**
	 * Returns how many total rows were selected in all queries together
	 * @return int
	 */
	public function SelectedRowsTotal() {
		return $this->selected_rows_total;
	}
	/**
	 * Returns how many rows were affected by last query
	 * @return int
	 */
	public function AffectedRows() {
		return $this->affected_rows;
	}
	/**
	 * Returns how many total rows were affected in all queries together
	 * @return int
	 */
	public function AffectedRowsTotal() {
		return $this->affected_rows_total;
	}
	/**
	 * Returns if there is a transaction active currently
	 * @return boolean
	 */
	public function InTransaction() {
		return $this->in_transaction;
	}
	/**
	 * Returns how many queries were run
	 * @return int
	 */
	public function TotalQueries() {
		return $this->queries;
	}
	/**
	 * Adds query to debugging array using the time_spent, affected_rows and selected_rows private variables set by last query
	 * @param string $query Mysql query that was run
	 */
	private function AddDebugData($query) {
		if ($this->debug) {
			$debug = array('query' => $query, 'time_spent' => number_format($this->time_spent, 5), 'affected_rows' => $this->affected_rows, 'selected_rows' => $this->selected_rows);
			if ($this->debugtrace) {
				$basedirlen = strlen(dirname(dirname(dirname(__FILE__)))) + 1;
				$bt = debug_backtrace();
				//remove AddDebugData call from the end
				array_shift($bt);
				//if previous call is wrapper from static mysql to dynmysl, remove it
				if (basename($bt[0]['file']) == 'Mysql.php' && $bt[0]['class'] == 'DynMysql')
					array_shift($bt);
				//remove first call (last element in stack) if it's just calling base.php
				if (basename($bt[count($bt) - 2]['file']) == 'base.php') {
					array_pop($bt);
					array_pop($bt);
				}
				$bstr = '';
				foreach ($bt as &$trace) {
					$bstr .= "\n" . (isset($trace['class']) ? $trace['class'] . (isset($trace['object']) ? '->' : '::') : '') . $trace['function'] . (isset($trace['file']) ? ' called from ' . substr($trace['file'], $basedirlen) . (isset($trace['line']) ? ' at line ' . $trace['line'] : '') : '' );
					//$bstr .= "\n" . basename($arr['file']) . ' called ' . (isset($arr['class']) ? $arr['class'] . '::' : '') . $arr['function'] . ' at line ' . $arr['line'];
				}
				$debug['trace'] = $bstr;
			}
			$this->queryarr[] = $debug;
		}
	}
	/**
	 * Sets the Mysql class debug mode (in debug mode, queryes and their related values are stored and can be viewed by calling ToString method)
	 *
	 * @param boolean $debug
	 * @param boolean $debugtrace
	 */
	public function SetDebug($debug = false, $debugtrace = false) {
		$this->debug = !!$debug;
		$this->debugtrace = $this->debug && !!$debugtrace;
	}
	/**
	 * Returns data about executed mysql queries in string form, to get more detailed data about each queries(spent time, affected rows etc), use SetDebug(true) before making any queries
	 * @return string
	 */
	public function ToString() {
		$qstr = 'not available, set debug to true to see more data';
		if ($this->debug) {
			$qstr = "\r\n" . self::ArrToString($this->queryarr) . "\r\n";
		}
		return sprintf("\t(mysql: queries:%s\t time_spent_total:%.08f\t sel_rows_total:%s\t aff_rows_total:%s\t queries: %s)", $this->queries, $this->time_spent_total, $this->selected_rows_total, $this->affected_rows_total, $qstr);
	}
	/**
	 * Converts an array to string similar to print_r but instead of outputting it directly, it is returned as a function result
	 *
	 * @param array $arr Array to convert to string representation
	 * @param int $level Reperesents the depth of recursion
	 * @return string String representation of array
	 */
	private static function ArrToString(&$arr, $level = 0) {
		$str = '';
		$pad = '';
		for ($i = 0; $i < $level; $i++)
			$pad .= '  ';
		if (is_array($arr)) {
			$str .= "Array(\r\n";
			foreach ($arr as $k => $v) {
				$str .= $pad . '  [' . $k . '] => ' . self::ArrToString($v, $level + 1);
			}
			$str .= "$pad)\r\n";
		} else {
			return $arr . "\r\n";
		}
		return $str;
	}
	/**
	 * Frees up mysql resultset
	 */
	private function FreeResult() {
		if ($this->res) {
			$this->db_free_result($this->res);
			$this->res = null;
		}
	}
	public function SetTimezone($timezone) {
		return !!$this->Query('SET time_zone="' . $this->EscapeString($timezone) . '"');
	}
	public function SetCharset($set = 'utf8') {
		if (!$this->db_set_charset($set)) {
			$set = addslashes($set);
			$this->db_query("SET character_set_results = '$set', character_set_client = '$set', character_set_connection = '$set', character_set_database = '$set', character_set_server = '$set'");
		}
	}
	public function SetTriggerError($new = true) {
		$this->trigger_error = $new;
	}
}
class MysqlIterator implements SeekableIterator, Countable {
	private $mysqlResult = null;
	private $currentRow = null;
	private $index = 0;
	private $count = 0;
	private $query = null;
	public function __construct($result) {
		trigger_error('Deprecated?!');
		//if query string, execute query and store result
		if (is_string($result)) {
			$this->query = $result;
			$result = Mysql::Query($result);
		}
		$this->mysqlResult = $result;
		$this->count = mysql_num_rows($result);
		$this->index = 0;
		$this->currentRow = null;
	}
	public function seek($index) {
		$this->index = $index;
		return mysql_data_seek($this->mysqlResult, $index);
	}
	public function next() {
		$this->currentRow = mysql_fetch_array($this->mysqlResult, MYSQL_ASSOC);
		$this->index += 1;
		return $this->currentRow;
	}
	public function current() {
		return $this->currentRow;
	}
	public function valid() {
		return $this->index < $this->count;
	}
	public function rewind() {
		mysql_data_seek($this->mysqlResult, 0);
		$this->currentRow = $this->next();
		$this->index = 0;
	}
	public function key() {
		return $this->index;
	}
	public function count() {
		return $this->count;
	}
	public function __destruct() {
		if ($this->mysqlResult) {
			mysql_free_result($this->mysqlResult);
			$this->mysqlResult = null;
		}
	}
	public function __sleep() {
		$this->__destruct();
	}
	public function __wakeup() {
		if ($this->query) {
			$this->mysqlResult = Mysql::Query($this->query);
			$this->count = mysql_num_rows($this->mysqlResult);
		}
		$old = $this->index;
		$this->seek($old);
		$this->currentObj = $this->next();
		$this->seek($old);
	}
}
 |