connection = @mysqli_connect($server, $user, $password, $name); // Give up if we have a connection error. if (mysqli_connect_error()) { header('HTTP/1.1 503 Service Temporarily Unavailable'); echo '

Database Connection Problems

Our software could not connect to the database. We apologise for any inconvenience and ask you to check back later.

'; exit; } $this->query('SET NAMES {string:utf8mb4}', ['utf8mb4' => 'utf8mb4']); } public function getQueryCount() { return $this->query_count; } public function getLoggedQueries() { return $this->logged_queries; } /** * Fetches a row from a given recordset, using field names as keys. */ public function fetch_assoc($resource) { return mysqli_fetch_assoc($resource); } /** * Fetches a row from a given recordset, encapsulating into an object. */ public function fetch_object($resource, $class) { return mysqli_fetch_object($resource, $class); } /** * Fetches a row from a given recordset, using numeric keys. */ public function fetch_row($resource) { return mysqli_fetch_row($resource); } /** * Destroys a given recordset. */ public function free_result($resource) { return mysqli_free_result($resource); } public function data_seek($result, $row_num) { return mysqli_data_seek($result, $row_num); } /** * Returns the amount of rows in a given recordset. */ public function num_rows($resource) { return mysqli_num_rows($resource); } /** * Returns the amount of fields in a given recordset. */ public function num_fields($resource) { return mysqli_num_fields($resource); } /** * Escapes a string. */ public function escape_string($string) { return mysqli_real_escape_string($this->connection, $string); } /** * Unescapes a string. */ public function unescape_string($string) { return stripslashes($string); } /** * Returns the last MySQL error. */ public function error() { return mysqli_error($this->connection); } public function server_info() { return mysqli_get_server_info($this->connection); } /** * Selects a database on a given connection. */ public function select_db($database) { return mysqli_select_db($database, $this->connection); } /** * Returns the amount of rows affected by the previous query. */ public function affected_rows() { return mysqli_affected_rows($this->connection); } /** * Returns the id of the row created by a previous query. */ public function insert_id() { return mysqli_insert_id($this->connection); } /** * Do a MySQL transaction. */ public function transaction($operation = 'commit') { switch ($operation) { case 'begin': case 'rollback': case 'commit': return @mysqli_query($this->connection, strtoupper($operation)); default: return false; } } /** * Function used as a callback for the preg_match function that parses variables into database queries. */ private function replacement_callback($matches) { list ($values, $connection) = $this->db_callback; if (!isset($matches[2])) trigger_error('Invalid value inserted or no type specified.', E_USER_ERROR); if (!isset($values[$matches[2]])) trigger_error('The database value you\'re trying to insert does not exist: ' . htmlspecialchars($matches[2]), E_USER_ERROR); $replacement = $values[$matches[2]]; switch ($matches[1]) { case 'int': if ((!is_numeric($replacement) || (string) $replacement !== (string) (int) $replacement) && $replacement !== 'NULL') trigger_error('Wrong value type sent to the database for field: ' . $matches[2] . '. Integer expected.', E_USER_ERROR); return $replacement !== 'NULL' ? (string) (int) $replacement : 'NULL'; break; case 'string': case 'text': return $replacement !== 'NULL' ? sprintf('\'%1$s\'', mysqli_real_escape_string($connection, $replacement)) : 'NULL'; break; case 'array_int': if (is_array($replacement)) { if (empty($replacement)) trigger_error('Database error, given array of integer values is empty.', E_USER_ERROR); foreach ($replacement as $key => $value) { if (!is_numeric($value) || (string) $value !== (string) (int) $value) trigger_error('Wrong value type sent to the database for field: ' . $matches[2] . '. Array of integers expected.', E_USER_ERROR); $replacement[$key] = (string) (int) $value; } return implode(', ', $replacement); } else trigger_error('Wrong value type sent to the database for field: ' . $matches[2] . '. Array of integers expected.', E_USER_ERROR); break; case 'array_string': if (is_array($replacement)) { if (empty($replacement)) trigger_error('Database error, given array of string values is empty.', E_USER_ERROR); foreach ($replacement as $key => $value) $replacement[$key] = sprintf('\'%1$s\'', mysqli_real_escape_string($connection, $value)); return implode(', ', $replacement); } else trigger_error('Wrong value type sent to the database for field: ' . $matches[2] . '. Array of strings expected.', E_USER_ERROR); break; case 'date': if (preg_match('~^(\d{4})-([0-1]?\d)-([0-3]?\d)$~', $replacement, $date_matches) === 1) return sprintf('\'%04d-%02d-%02d\'', $date_matches[1], $date_matches[2], $date_matches[3]); elseif ($replacement === 'NULL') return 'NULL'; else trigger_error('Wrong value type sent to the database for field: ' . $matches[2] . '. Date expected.', E_USER_ERROR); break; case 'datetime': if (is_a($replacement, 'DateTime')) return $replacement->format('\'Y-m-d H:i:s\''); elseif (preg_match('~^(\d{4})-([0-1]?\d)-([0-3]?\d) (\d{2}):(\d{2}):(\d{2})$~', $replacement, $date_matches) === 1) return sprintf('\'%04d-%02d-%02d %02d:%02d:%02d\'', $date_matches[1], $date_matches[2], $date_matches[3], $date_matches[4], $date_matches[5], $date_matches[6]); elseif ($replacement === 'NULL') return 'NULL'; else trigger_error('Wrong value type sent to the database for field: ' . $matches[2] . '. DateTime expected.', E_USER_ERROR); break; case 'float': if (!is_numeric($replacement) && $replacement !== 'NULL') trigger_error('Wrong value type sent to the database for field: ' . $matches[2] . '. Floating point number expected.', E_USER_ERROR); return $replacement !== 'NULL' ? (string) (float) $replacement : 'NULL'; break; case 'identifier': // Backticks inside identifiers are supported as of MySQL 4.1. We don't need them here. return '`' . strtr($replacement, ['`' => '', '.' => '']) . '`'; break; case 'raw': return $replacement; break; case 'bool': case 'boolean': // In mysql this is a synonym for tinyint(1) return (bool)$replacement ? 1 : 0; break; default: trigger_error('Undefined type ' . $matches[1] . ' used in the database query', E_USER_ERROR); break; } } /** * Escapes and quotes a string using values passed, and executes the query. */ public function query($db_string, $db_values = []) { // One more query.... $this->query_count ++; // Overriding security? This is evil! $security_override = $db_values === 'security_override' || !empty($db_values['security_override']); // Please, just use new style queries. if (strpos($db_string, '\'') !== false && !$security_override) trigger_error('Hack attempt!', 'Illegal character (\') used in query.', E_USER_ERROR); if (!$security_override && !empty($db_values)) { // Set some values for use in the callback function. $this->db_callback = [$db_values, $this->connection]; // Insert the values passed to this function. $db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', [&$this, 'replacement_callback'], $db_string); // Save some memory. $this->db_callback = []; } if (defined("DB_LOG_QUERIES") && DB_LOG_QUERIES) $this->logged_queries[] = $db_string; try { $return = @mysqli_query($this->connection, $db_string, empty($this->unbuffered) ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT); } catch (Exception $e) { $clean_sql = implode("\n", array_map('trim', explode("\n", $db_string))); trigger_error($this->error() . '
' . $clean_sql, E_USER_ERROR); } return $return; } /** * Escapes and quotes a string just like db_query, but does not execute the query. * Useful for debugging purposes. */ public function quote($db_string, $db_values = []) { // Please, just use new style queries. if (strpos($db_string, '\'') !== false) trigger_error('Hack attempt!', 'Illegal character (\') used in query.', E_USER_ERROR); // Save some values for use in the callback function. $this->db_callback = [$db_values, $this->connection]; // Insert the values passed to this function. $db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', [&$this, 'replacement_callback'], $db_string); // Save some memory. $this->db_callback = []; return $db_string; } /** * Executes a query, returning an object of the row it returns. */ public function queryObject($class, $db_string, $db_values = []) { $res = $this->query($db_string, $db_values); if (!$res || $this->num_rows($res) == 0) return null; $object = $this->fetch_object($res, $class); $this->free_result($res); return $object; } /** * Executes a query, returning an array of objects of all the rows returns. */ public function queryObjects($class, $db_string, $db_values = []) { $res = $this->query($db_string, $db_values); if (!$res || $this->num_rows($res) == 0) return []; $rows = []; while ($object = $this->fetch_object($res, $class)) $rows[] = $object; $this->free_result($res); return $rows; } /** * Executes a query, returning an array of all the rows it returns. */ public function queryRow($db_string, $db_values = []) { $res = $this->query($db_string, $db_values); if (!$res || $this->num_rows($res) == 0) return []; $row = $this->fetch_row($res); $this->free_result($res); return $row; } /** * Executes a query, returning an array of all the rows it returns. */ public function queryRows($db_string, $db_values = []) { $res = $this->query($db_string, $db_values); if (!$res || $this->num_rows($res) == 0) return []; $rows = []; while ($row = $this->fetch_row($res)) $rows[] = $row; $this->free_result($res); return $rows; } /** * Executes a query, returning an array of all the rows it returns. */ public function queryPair($db_string, $db_values = []) { $res = $this->query($db_string, $db_values); if (!$res || $this->num_rows($res) == 0) return []; $rows = []; while ($row = $this->fetch_row($res)) $rows[$row[0]] = $row[1]; $this->free_result($res); return $rows; } /** * Executes a query, returning an array of all the rows it returns. */ public function queryPairs($db_string, $db_values = []) { $res = $this->query($db_string, $db_values); if (!$res || $this->num_rows($res) == 0) return []; $rows = []; while ($row = $this->fetch_assoc($res)) { $key_value = reset($row); $rows[$key_value] = $row; } $this->free_result($res); return $rows; } /** * Executes a query, returning an associative array of all the rows it returns. */ public function queryAssoc($db_string, $db_values = []) { $res = $this->query($db_string, $db_values); if (!$res || $this->num_rows($res) == 0) return []; $row = $this->fetch_assoc($res); $this->free_result($res); return $row; } /** * Executes a query, returning an associative array of all the rows it returns. */ public function queryAssocs($db_string, $db_values = [], $connection = null) { $res = $this->query($db_string, $db_values); if (!$res || $this->num_rows($res) == 0) return []; $rows = []; while ($row = $this->fetch_assoc($res)) $rows[] = $row; $this->free_result($res); return $rows; } /** * Executes a query, returning the first value of the first row. */ public function queryValue($db_string, $db_values = []) { $res = $this->query($db_string, $db_values); // If this happens, you're doing it wrong. if (!$res || $this->num_rows($res) == 0) return null; list($value) = $this->fetch_row($res); $this->free_result($res); return $value; } /** * Executes a query, returning an array of the first value of each row. */ public function queryValues($db_string, $db_values = []) { $res = $this->query($db_string, $db_values); if (!$res || $this->num_rows($res) == 0) return []; $rows = []; while ($row = $this->fetch_row($res)) $rows[] = $row[0]; $this->free_result($res); return $rows; } /** * This function can be used to insert data into the database in a secure way. */ public function insert($method, $table, $columns, $data) { // With nothing to insert, simply return. if (empty($data)) return; // Inserting data as a single row can be done as a single array. if (!is_array($data[array_rand($data)])) $data = [$data]; // Create the mold for a single row insert. $insertData = '('; foreach ($columns as $columnName => $type) { // Are we restricting the length? if (strpos($type, 'string-') !== false) $insertData .= sprintf('SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName); else $insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName); } $insertData = substr($insertData, 0, -2) . ')'; // Create an array consisting of only the columns. $indexed_columns = array_keys($columns); // Here's where the variables are injected to the query. $insertRows = []; foreach ($data as $dataRow) $insertRows[] = $this->quote($insertData, array_combine($indexed_columns, $dataRow)); // Determine the method of insertion. $queryTitle = $method === 'replace' ? 'REPLACE' : ($method === 'ignore' ? 'INSERT IGNORE' : 'INSERT'); // Do the insert. return $this->query(' ' . $queryTitle . ' INTO ' . $table . ' (`' . implode('`, `', $indexed_columns) . '`) VALUES ' . implode(', ', $insertRows), ['security_override' => true]); } }