require_once INCLUDES_DIR."/class/Query.php"; # File: QueryBuilder # See Also: # require_once INCLUDES_DIR."/class/QueryBuilder.php"; class SQL { # Array: $debug # Holds debug information for SQL queries. public $debug = array(); # Integer: $queries # Number of queries it takes to load the page. public $queries = 0; # Variable: $db # Holds the currently running database. public $db; # Variable: $error # Holds an error message from the last attempted query. public $error = ""; # Boolean: $silence_errors # Ignore errors? public $silence_errors = false; /** * Function: __construct * The class constructor is private so there is only one connection. * * Parameters: * $settings - Settings to load instead of the config. */ private function __construct($settings = array()) { if (!UPGRADING and !INSTALLING and !isset(Config::current()->sql)) error(__("Error"), __("Database configuration is not set. Please run the upgrader.")); $database = !UPGRADING ? oneof(@Config::current()->sql, array()) : Config::get("sql") ; if (is_array($settings)) fallback($database, $settings); elseif ($settings === true) $this->silence_errors = true; if (!empty($database)) foreach ($database as $setting => $value) $this->$setting = $value; $this->connected = false; # We really don't need PDO anymore, since we have the two we supported with it hardcoded (kinda). # Keeping this here for when/if we decide to add support for more database engines, like Postgres and MSSQL. # if (class_exists("PDO") and (in_array("mysql", PDO::getAvailableDrivers()) or in_array("sqlite", PDO::getAvailableDrivers()))) # return $this->method = "pdo"; if (isset($this->adapter)) { if ($this->adapter == "mysql" and class_exists("MySQLi")) $this->method = "mysqli"; elseif ($this->adapter == "mysql" and function_exists("mysql_connect")) $this->method = "mysql"; elseif (class_exists("PDO") and ($this->adapter == "sqlite" and in_array("sqlite", PDO::getAvailableDrivers()) or $this->adapter == "pgsql" and in_array("pgsql", PDO::getAvailableDrivers()))) $this->method = "pdo"; } else if (class_exists("MySQLi")) $this->method = "mysqli"; elseif (function_exists("mysql_connect")) $this->method = "mysql"; elseif (class_exists("PDO") and in_array("mysql", PDO::getAvailableDrivers())) $this->method = "pdo"; } /** * Function: set * Sets a variable's value. * * Parameters: * $setting - The setting name. * $value - The new value. Can be boolean, numeric, an array, a string, etc. * $overwrite - If the setting exists and is the same value, should it be overwritten? */ public function set($setting, $value, $overwrite = true) { if (isset($this->$setting) and $this->$setting == $value and !$overwrite and !UPGRADING) return false; if (!UPGRADING) $config = Config::current(); $database = (!UPGRADING) ? fallback($config->sql, array()) : Config::get("sql") ; # Add the setting $database[$setting] = $this->$setting = $value; return (!UPGRADING) ? $config->set("sql", $database) : Config::set("sql", $database) ; } /** * Function: connect * Connects to the SQL database. * * Parameters: * $checking - Return a boolean of whether or not it could connect, instead of showing an error. */ public function connect($checking = false) { if ($this->connected) return true; if (!isset($this->database)) self::__construct(); if (UPGRADING) $checking = true; switch($this->method) { case "pdo": try { if (empty($this->database)) throw new PDOException("No database specified."); if ($this->adapter == "sqlite") { $this->db = new PDO("sqlite:".$this->database, null, null, array(PDO::ATTR_PERSISTENT => true)); $this->db->sqliteCreateFunction("YEAR", array($this, "year_from_datetime"), 1); $this->db->sqliteCreateFunction("MONTH", array($this, "month_from_datetime"), 1); $this->db->sqliteCreateFunction("DAY", array($this, "day_from_datetime"), 1); $this->db->sqliteCreateFunction("HOUR", array($this, "hour_from_datetime"), 1); $this->db->sqliteCreateFunction("MINUTE", array($this, "minute_from_datetime"), 1); $this->db->sqliteCreateFunction("SECOND", array($this, "second_from_datetime"), 1); } else $this->db = new PDO($this->adapter.":host=".$this->host.";".((isset($this->port)) ? "port=".$this->port.";" : "")."dbname=".$this->database, $this->username, $this->password, array(PDO::ATTR_PERSISTENT => true)); $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch (PDOException $error) { $this->error = $error->getMessage(); return ($checking) ? false : error(__("Database Error"), $this->error) ; } break; case "mysqli": $this->db = @new MySQLi($this->host, $this->username, $this->password, $this->database); $this->error = mysqli_connect_error(); if (mysqli_connect_errno()) return ($checking) ? false : error(__("Database Error"), $this->error) ; break; case "mysql": $this->db = @mysql_connect($this->host, $this->username, $this->password); $this->error = mysql_error(); if (!$this->db or !@mysql_select_db($this->database)) return ($checking) ? false : error(__("Database Error"), $this->error) ; break; } if ($this->adapter == "mysql") new Query($this, "SET NAMES 'utf8'"); # Note: This doesn't increase the query debug/count. return $this->connected = true; } /** * Function: query * Executes a query and increases $queries>. * If the query results in an error, it will die and show the error. * * Parameters: * $query - Query to execute. * $params - An associative array of parameters used in the query. * $throw_exceptions - Should an exception be thrown if the query fails? */ public function query($query, $params = array(), $throw_exceptions = false) { if (!$this->connected) return false; # Ensure that every param in $params exists in the query. # If it doesn't, remove it from $params. foreach ($params as $name => $val) if (!strpos($query, $name)) unset($params[$name]); $query = str_replace("__", $this->prefix, $query); if ($this->adapter == "sqlite") $query = str_ireplace(" DEFAULT CHARSET=utf8", "", str_ireplace("AUTO_INCREMENT", "AUTOINCREMENT", $query)); if ($this->adapter == "pgsql") $query = str_ireplace(array("CREATE TABLE IF NOT EXISTS", "INTEGER PRIMARY KEY AUTO_INCREMENT", ") DEFAULT CHARSET=utf8", "TINYINT", "DATETIME", "DEFAULT '0000-00-00 00:00:00'", "LONGTEXT", "REPLACE INTO"), array("CREATE TABLE", "SERIAL PRIMARY KEY", ")", "SMALLINT", "TIMESTAMP", "", "TEXT", "INSERT INTO"), $query); $query = new Query($this, $query, $params, $throw_exceptions); return (!$query->query and UPGRADING) ? false : $query ; } /** * Function: count * Performs a counting query and returns the number of matching rows. * * Parameters: * $tables - An array (or string) of tables to count results on. * $conds - An array (or string) of conditions to match. * $params - An associative array of parameters used in the query. * $throw_exceptions - Should exceptions be thrown on error? */ public function count($tables, $conds = null, $params = array(), $throw_exceptions = false) { $query = $this->query(QueryBuilder::build_count($tables, $conds, $params), $params, $throw_exceptions); return ($query->query) ? $query->fetchColumn() : false ; } /** * Function: select * Performs a SELECT with given criteria and returns the query result object. * * Parameters: * $tables - An array (or string) of tables to grab results from. * $fields - Fields to select. * $conds - An array (or string) of conditions to match. * $order - ORDER BY statement. Can be an array. * $params - An associative array of parameters used in the query. * $limit - Limit for results. * $offset - Offset for the select statement. * $group - GROUP BY statement. Can be an array. * $left_join - An array of additional LEFT JOINs. * $throw_exceptions - Should exceptions be thrown on error? */ public function select($tables, $fields = "*", $conds = null, $order = null, $params = array(), $limit = null, $offset = null, $group = null, $left_join = array(), $throw_exceptions = false) { return $this->query(QueryBuilder::build_select($tables, $fields, $conds, $order, $limit, $offset, $group, $left_join, $params), $params, $throw_exceptions); } /** * Function: insert * Performs an INSERT with given data. * * Parameters: * $table - Table to insert to. * $data - An associative array of data to insert. * $params - An associative array of parameters used in the query. * $throw_exceptions - Should exceptions be thrown on error? */ public function insert($table, $data, $params = array(), $throw_exceptions = false) { return $this->query(QueryBuilder::build_insert($table, $data, $params), $params, $throw_exceptions); } /** * Function: replace * Performs either an INSERT or an UPDATE depending on * whether a row exists with the specified keys matching * their values in the data. * * Parameters: * $table - Table to update or insert into. * $keys - Columns to match on. * $data - Data for the insert and value matches for the keys. * $params - An associative array of parameters to be used in the query. * $throw_exceptions - Should exceptions be thrown on error? */ public function replace($table, $keys, $data, $params = array(), $throw_exceptions = false) { $match = array(); foreach ((array) $keys as $key) $match[$key] = $data[$key]; if ($this->count($table, $match, $params)) $this->update($table, $match, $data, $params, $throw_exceptions); else $this->insert($table, $data, $params, $throw_exceptions); } /** * Function: update * Performs an UDATE with given criteria and data. * * Parameters: * $table - Table to update. * $conds - Rows to update. * $data - An associative array of data to update. * $params - An associative array of parameters used in the query. * $throw_exceptions - Should exceptions be thrown on error? */ public function update($table, $conds, $data, $params = array(), $throw_exceptions = false) { return $this->query(QueryBuilder::build_update($table, $conds, $data, $params), $params, $throw_exceptions); } /** * Function: delete * Performs a DELETE with given criteria. * * Parameters: * $table - Table to delete from. * $conds - Rows to delete.. * $params - An associative array of parameters used in the query. * $throw_exceptions - Should exceptions be thrown on error? */ public function delete($table, $conds, $params = array(), $throw_exceptions = false) { return $this->query(QueryBuilder::build_delete($table, $conds, $params), $params, $throw_exceptions); } /** * Function: latest * Returns the last inserted sequential value. * Both function arguments are only relevant for PostgreSQL. * * Parameters: * $table - Table to get the latest value from. * $seq - Name of the sequence. */ public function latest($table, $seq = "id_seq") { if (!isset($this->db)) $this->connect(); switch($this->method) { case "pdo": return $this->db->lastInsertId($this->prefix.$table."_".$seq); break; case "mysqli": return $this->db->insert_id; break; case "mysql": return @mysql_insert_id(); break; } } /** * Function: escape * Escapes a string, escaping things like $1 and C:\foo\bar so that they don't get borked by the preg_replace. * * This also handles calling the SQL connection method's "escape_string" functions. * * Parameters: * $string - String to escape. * $quotes - Auto-wrap the string in quotes (@'@)? */ public function escape($string, $quotes = true) { if (!isset($this->db)) $this->connect(); switch($this->method) { case "pdo": $string = ltrim(rtrim($this->db->quote($string), "'"), "'"); break; case "mysqli": $string = $this->db->escape_string($string); break; case "mysql": $string = mysql_real_escape_string($string); break; } # I don't think this ever worked how it intended. # I've tested PDO, MySQLi, and MySQL and they all # properly escape with this disabled, but get double # escaped with this uncommented: # $string = str_replace('\\', '\\\\', $string); $string = str_replace('$', '\$', $string); if ($quotes and !is_numeric($string)) $string = "'".$string."'"; return $string; } /** * Function: year_from_datetime * Returns the year of a datetime. * * Parameters: * $datetime - DATETIME value. */ public function year_from_datetime($datetime) { return when("Y", $datetime); } /** * Function: month_from_datetime * Returns the month of a datetime. * * Parameters: * $datetime - DATETIME value. */ public function month_from_datetime($datetime) { return when("m", $datetime); } /** * Function: day_from_datetime * Returns the day of a datetime. * * Parameters: * $datetime - DATETIME value. */ public function day_from_datetime($datetime) { return when("d", $datetime); } /** * Function: hour_from_datetime * Returns the hour of a datetime. * * Parameters: * $datetime - DATETIME value. */ public function hour_from_datetime($datetime) { return when("g", $datetime); } /** * Function: minute_from_datetime * Returns the minute of a datetime. * * Parameters: * $datetime - DATETIME value. */ public function minute_from_datetime($datetime) { return when("i", $datetime); } /** * Function: second_from_datetime * Returns the second of a datetime. * * Parameters: * $datetime - DATETIME value. */ public function second_from_datetime($datetime) { return when("s", $datetime); } /** * Function: current * Returns a singleton reference to the current connection. */ public static function & current($settings = false) { if ($settings) { static $loaded_settings = null; return $loaded_settings = new self($settings); } else { static $instance = null; return $instance = (empty($instance)) ? new self() : $instance ; } } }