KXStudio Website https://kx.studio/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SQL.php 19KB

9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. <?php
  2. /**
  3. * Class: SQL
  4. * Contains the database settings and functions for interacting with the SQL database.
  5. */
  6. # File: Query
  7. # See Also:
  8. # <Query>
  9. require_once INCLUDES_DIR."/class/Query.php";
  10. # File: QueryBuilder
  11. # See Also:
  12. # <QueryBuilder>
  13. require_once INCLUDES_DIR."/class/QueryBuilder.php";
  14. class SQL {
  15. # Array: $debug
  16. # Holds debug information for SQL queries.
  17. public $debug = array();
  18. # Integer: $queries
  19. # Number of queries it takes to load the page.
  20. public $queries = 0;
  21. # Variable: $db
  22. # Holds the currently running database.
  23. public $db;
  24. # Variable: $error
  25. # Holds an error message from the last attempted query.
  26. public $error = "";
  27. # Boolean: $silence_errors
  28. # Ignore errors?
  29. public $silence_errors = false;
  30. /**
  31. * Function: __construct
  32. * The class constructor is private so there is only one connection.
  33. *
  34. * Parameters:
  35. * $settings - Settings to load instead of the config.
  36. */
  37. private function __construct($settings = array()) {
  38. if (!UPGRADING and !INSTALLING and !isset(Config::current()->sql))
  39. error(__("Error"), __("Database configuration is not set. Please run the upgrader."));
  40. $database = !UPGRADING ?
  41. oneof(@Config::current()->sql, array()) :
  42. Config::get("sql") ;
  43. if (is_array($settings))
  44. fallback($database, $settings);
  45. elseif ($settings === true)
  46. $this->silence_errors = true;
  47. if (!empty($database))
  48. foreach ($database as $setting => $value)
  49. $this->$setting = $value;
  50. $this->connected = false;
  51. # We really don't need PDO anymore, since we have the two we supported with it hardcoded (kinda).
  52. # Keeping this here for when/if we decide to add support for more database engines, like Postgres and MSSQL.
  53. # if (class_exists("PDO") and (in_array("mysql", PDO::getAvailableDrivers()) or in_array("sqlite", PDO::getAvailableDrivers())))
  54. # return $this->method = "pdo";
  55. if (isset($this->adapter)) {
  56. if ($this->adapter == "mysql" and class_exists("MySQLi"))
  57. $this->method = "mysqli";
  58. elseif ($this->adapter == "mysql" and function_exists("mysql_connect"))
  59. $this->method = "mysql";
  60. elseif (class_exists("PDO") and
  61. ($this->adapter == "sqlite" and in_array("sqlite", PDO::getAvailableDrivers()) or
  62. $this->adapter == "pgsql" and in_array("pgsql", PDO::getAvailableDrivers())))
  63. $this->method = "pdo";
  64. } else
  65. if (class_exists("MySQLi"))
  66. $this->method = "mysqli";
  67. elseif (function_exists("mysql_connect"))
  68. $this->method = "mysql";
  69. elseif (class_exists("PDO") and in_array("mysql", PDO::getAvailableDrivers()))
  70. $this->method = "pdo";
  71. }
  72. /**
  73. * Function: set
  74. * Sets a variable's value.
  75. *
  76. * Parameters:
  77. * $setting - The setting name.
  78. * $value - The new value. Can be boolean, numeric, an array, a string, etc.
  79. * $overwrite - If the setting exists and is the same value, should it be overwritten?
  80. */
  81. public function set($setting, $value, $overwrite = true) {
  82. if (isset($this->$setting) and $this->$setting == $value and !$overwrite and !UPGRADING)
  83. return false;
  84. if (!UPGRADING)
  85. $config = Config::current();
  86. $database = (!UPGRADING) ? fallback($config->sql, array()) : Config::get("sql") ;
  87. # Add the setting
  88. $database[$setting] = $this->$setting = $value;
  89. return (!UPGRADING) ? $config->set("sql", $database) : Config::set("sql", $database) ;
  90. }
  91. /**
  92. * Function: connect
  93. * Connects to the SQL database.
  94. *
  95. * Parameters:
  96. * $checking - Return a boolean of whether or not it could connect, instead of showing an error.
  97. */
  98. public function connect($checking = false) {
  99. if ($this->connected)
  100. return true;
  101. if (!isset($this->database))
  102. self::__construct();
  103. if (UPGRADING)
  104. $checking = true;
  105. switch($this->method) {
  106. case "pdo":
  107. try {
  108. if (empty($this->database))
  109. throw new PDOException("No database specified.");
  110. if ($this->adapter == "sqlite") {
  111. $this->db = new PDO("sqlite:".$this->database, null, null, array(PDO::ATTR_PERSISTENT => true));
  112. $this->db->sqliteCreateFunction("YEAR", array($this, "year_from_datetime"), 1);
  113. $this->db->sqliteCreateFunction("MONTH", array($this, "month_from_datetime"), 1);
  114. $this->db->sqliteCreateFunction("DAY", array($this, "day_from_datetime"), 1);
  115. $this->db->sqliteCreateFunction("HOUR", array($this, "hour_from_datetime"), 1);
  116. $this->db->sqliteCreateFunction("MINUTE", array($this, "minute_from_datetime"), 1);
  117. $this->db->sqliteCreateFunction("SECOND", array($this, "second_from_datetime"), 1);
  118. } else
  119. $this->db = new PDO($this->adapter.":host=".$this->host.";".((isset($this->port)) ? "port=".$this->port.";" : "")."dbname=".$this->database,
  120. $this->username,
  121. $this->password,
  122. array(PDO::ATTR_PERSISTENT => true));
  123. $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  124. } catch (PDOException $error) {
  125. $this->error = $error->getMessage();
  126. return ($checking) ? false : error(__("Database Error"), $this->error) ;
  127. }
  128. break;
  129. case "mysqli":
  130. $this->db = @new MySQLi($this->host, $this->username, $this->password, $this->database);
  131. $this->error = mysqli_connect_error();
  132. if (mysqli_connect_errno())
  133. return ($checking) ? false : error(__("Database Error"), $this->error) ;
  134. break;
  135. case "mysql":
  136. $this->db = @mysql_connect($this->host, $this->username, $this->password);
  137. $this->error = mysql_error();
  138. if (!$this->db or !@mysql_select_db($this->database))
  139. return ($checking) ? false : error(__("Database Error"), $this->error) ;
  140. break;
  141. }
  142. if ($this->adapter == "mysql")
  143. new Query($this, "SET NAMES 'utf8'"); # Note: This doesn't increase the query debug/count.
  144. return $this->connected = true;
  145. }
  146. /**
  147. * Function: query
  148. * Executes a query and increases <SQL->$queries>.
  149. * If the query results in an error, it will die and show the error.
  150. *
  151. * Parameters:
  152. * $query - Query to execute.
  153. * $params - An associative array of parameters used in the query.
  154. * $throw_exceptions - Should an exception be thrown if the query fails?
  155. */
  156. public function query($query, $params = array(), $throw_exceptions = false) {
  157. if (!$this->connected)
  158. return false;
  159. # Ensure that every param in $params exists in the query.
  160. # If it doesn't, remove it from $params.
  161. foreach ($params as $name => $val)
  162. if (!strpos($query, $name))
  163. unset($params[$name]);
  164. $query = str_replace("__", $this->prefix, $query);
  165. if ($this->adapter == "sqlite")
  166. $query = str_ireplace(" DEFAULT CHARSET=utf8", "", str_ireplace("AUTO_INCREMENT", "AUTOINCREMENT", $query));
  167. if ($this->adapter == "pgsql")
  168. $query = str_ireplace(array("CREATE TABLE IF NOT EXISTS",
  169. "INTEGER PRIMARY KEY AUTO_INCREMENT",
  170. ") DEFAULT CHARSET=utf8",
  171. "TINYINT",
  172. "DATETIME",
  173. "DEFAULT '0000-00-00 00:00:00'",
  174. "LONGTEXT",
  175. "REPLACE INTO"),
  176. array("CREATE TABLE",
  177. "SERIAL PRIMARY KEY",
  178. ")",
  179. "SMALLINT",
  180. "TIMESTAMP",
  181. "",
  182. "TEXT",
  183. "INSERT INTO"),
  184. $query);
  185. $query = new Query($this, $query, $params, $throw_exceptions);
  186. return (!$query->query and UPGRADING) ? false : $query ;
  187. }
  188. /**
  189. * Function: count
  190. * Performs a counting query and returns the number of matching rows.
  191. *
  192. * Parameters:
  193. * $tables - An array (or string) of tables to count results on.
  194. * $conds - An array (or string) of conditions to match.
  195. * $params - An associative array of parameters used in the query.
  196. * $throw_exceptions - Should exceptions be thrown on error?
  197. */
  198. public function count($tables, $conds = null, $params = array(), $throw_exceptions = false) {
  199. $query = $this->query(QueryBuilder::build_count($tables, $conds, $params), $params, $throw_exceptions);
  200. return ($query->query) ? $query->fetchColumn() : false ;
  201. }
  202. /**
  203. * Function: select
  204. * Performs a SELECT with given criteria and returns the query result object.
  205. *
  206. * Parameters:
  207. * $tables - An array (or string) of tables to grab results from.
  208. * $fields - Fields to select.
  209. * $conds - An array (or string) of conditions to match.
  210. * $order - ORDER BY statement. Can be an array.
  211. * $params - An associative array of parameters used in the query.
  212. * $limit - Limit for results.
  213. * $offset - Offset for the select statement.
  214. * $group - GROUP BY statement. Can be an array.
  215. * $left_join - An array of additional LEFT JOINs.
  216. * $throw_exceptions - Should exceptions be thrown on error?
  217. */
  218. public function select($tables, $fields = "*", $conds = null, $order = null, $params = array(), $limit = null, $offset = null, $group = null, $left_join = array(), $throw_exceptions = false) {
  219. return $this->query(QueryBuilder::build_select($tables, $fields, $conds, $order, $limit, $offset, $group, $left_join, $params), $params, $throw_exceptions);
  220. }
  221. /**
  222. * Function: insert
  223. * Performs an INSERT with given data.
  224. *
  225. * Parameters:
  226. * $table - Table to insert to.
  227. * $data - An associative array of data to insert.
  228. * $params - An associative array of parameters used in the query.
  229. * $throw_exceptions - Should exceptions be thrown on error?
  230. */
  231. public function insert($table, $data, $params = array(), $throw_exceptions = false) {
  232. return $this->query(QueryBuilder::build_insert($table, $data, $params), $params, $throw_exceptions);
  233. }
  234. /**
  235. * Function: replace
  236. * Performs either an INSERT or an UPDATE depending on
  237. * whether a row exists with the specified keys matching
  238. * their values in the data.
  239. *
  240. * Parameters:
  241. * $table - Table to update or insert into.
  242. * $keys - Columns to match on.
  243. * $data - Data for the insert and value matches for the keys.
  244. * $params - An associative array of parameters to be used in the query.
  245. * $throw_exceptions - Should exceptions be thrown on error?
  246. */
  247. public function replace($table, $keys, $data, $params = array(), $throw_exceptions = false) {
  248. $match = array();
  249. foreach ((array) $keys as $key)
  250. $match[$key] = $data[$key];
  251. if ($this->count($table, $match, $params))
  252. $this->update($table, $match, $data, $params, $throw_exceptions);
  253. else
  254. $this->insert($table, $data, $params, $throw_exceptions);
  255. }
  256. /**
  257. * Function: update
  258. * Performs an UDATE with given criteria and data.
  259. *
  260. * Parameters:
  261. * $table - Table to update.
  262. * $conds - Rows to update.
  263. * $data - An associative array of data to update.
  264. * $params - An associative array of parameters used in the query.
  265. * $throw_exceptions - Should exceptions be thrown on error?
  266. */
  267. public function update($table, $conds, $data, $params = array(), $throw_exceptions = false) {
  268. return $this->query(QueryBuilder::build_update($table, $conds, $data, $params), $params, $throw_exceptions);
  269. }
  270. /**
  271. * Function: delete
  272. * Performs a DELETE with given criteria.
  273. *
  274. * Parameters:
  275. * $table - Table to delete from.
  276. * $conds - Rows to delete..
  277. * $params - An associative array of parameters used in the query.
  278. * $throw_exceptions - Should exceptions be thrown on error?
  279. */
  280. public function delete($table, $conds, $params = array(), $throw_exceptions = false) {
  281. return $this->query(QueryBuilder::build_delete($table, $conds, $params), $params, $throw_exceptions);
  282. }
  283. /**
  284. * Function: latest
  285. * Returns the last inserted sequential value.
  286. * Both function arguments are only relevant for PostgreSQL.
  287. *
  288. * Parameters:
  289. * $table - Table to get the latest value from.
  290. * $seq - Name of the sequence.
  291. */
  292. public function latest($table, $seq = "id_seq") {
  293. if (!isset($this->db))
  294. $this->connect();
  295. switch($this->method) {
  296. case "pdo":
  297. return $this->db->lastInsertId($this->prefix.$table."_".$seq);
  298. break;
  299. case "mysqli":
  300. return $this->db->insert_id;
  301. break;
  302. case "mysql":
  303. return @mysql_insert_id();
  304. break;
  305. }
  306. }
  307. /**
  308. * Function: escape
  309. * Escapes a string, escaping things like $1 and C:\foo\bar so that they don't get borked by the preg_replace.
  310. *
  311. * This also handles calling the SQL connection method's "escape_string" functions.
  312. *
  313. * Parameters:
  314. * $string - String to escape.
  315. * $quotes - Auto-wrap the string in quotes (@'@)?
  316. */
  317. public function escape($string, $quotes = true) {
  318. if (!isset($this->db))
  319. $this->connect();
  320. switch($this->method) {
  321. case "pdo":
  322. $string = ltrim(rtrim($this->db->quote($string), "'"), "'");
  323. break;
  324. case "mysqli":
  325. $string = $this->db->escape_string($string);
  326. break;
  327. case "mysql":
  328. $string = mysql_real_escape_string($string);
  329. break;
  330. }
  331. # I don't think this ever worked how it intended.
  332. # I've tested PDO, MySQLi, and MySQL and they all
  333. # properly escape with this disabled, but get double
  334. # escaped with this uncommented:
  335. # $string = str_replace('\\', '\\\\', $string);
  336. $string = str_replace('$', '\$', $string);
  337. if ($quotes and !is_numeric($string))
  338. $string = "'".$string."'";
  339. return $string;
  340. }
  341. /**
  342. * Function: year_from_datetime
  343. * Returns the year of a datetime.
  344. *
  345. * Parameters:
  346. * $datetime - DATETIME value.
  347. */
  348. public function year_from_datetime($datetime) {
  349. return when("Y", $datetime);
  350. }
  351. /**
  352. * Function: month_from_datetime
  353. * Returns the month of a datetime.
  354. *
  355. * Parameters:
  356. * $datetime - DATETIME value.
  357. */
  358. public function month_from_datetime($datetime) {
  359. return when("m", $datetime);
  360. }
  361. /**
  362. * Function: day_from_datetime
  363. * Returns the day of a datetime.
  364. *
  365. * Parameters:
  366. * $datetime - DATETIME value.
  367. */
  368. public function day_from_datetime($datetime) {
  369. return when("d", $datetime);
  370. }
  371. /**
  372. * Function: hour_from_datetime
  373. * Returns the hour of a datetime.
  374. *
  375. * Parameters:
  376. * $datetime - DATETIME value.
  377. */
  378. public function hour_from_datetime($datetime) {
  379. return when("g", $datetime);
  380. }
  381. /**
  382. * Function: minute_from_datetime
  383. * Returns the minute of a datetime.
  384. *
  385. * Parameters:
  386. * $datetime - DATETIME value.
  387. */
  388. public function minute_from_datetime($datetime) {
  389. return when("i", $datetime);
  390. }
  391. /**
  392. * Function: second_from_datetime
  393. * Returns the second of a datetime.
  394. *
  395. * Parameters:
  396. * $datetime - DATETIME value.
  397. */
  398. public function second_from_datetime($datetime) {
  399. return when("s", $datetime);
  400. }
  401. /**
  402. * Function: current
  403. * Returns a singleton reference to the current connection.
  404. */
  405. public static function & current($settings = false) {
  406. if ($settings) {
  407. static $loaded_settings = null;
  408. return $loaded_settings = new self($settings);
  409. } else {
  410. static $instance = null;
  411. return $instance = (empty($instance)) ? new self() : $instance ;
  412. }
  413. }
  414. }