cache_db)) { if (($this->last_select_db === null) || ($this->last_select_db[1] !== $db_name)) { mysqli_select_db($this->cache_db[$x], $db_name); $this->last_select_db = [$this->cache_db[$x], $db_name]; } return [$this->cache_db[$x], $db_name]; } mysqli_report(MYSQLI_REPORT_OFF); // Connect $db_port = 3306; if (strpos($db_host, ':') !== false) { list($db_host, $_db_port) = explode(':', $db_host); $db_port = intval($_db_port); } $db_link = @mysqli_connect(($persistent ? 'p:' : '') . $db_host, $db_user, $db_password, '', $db_port); if ($db_link === false) { $error = 'Could not connect to database-server (when authenticating) (' . mysqli_connect_error() . ')'; if ($fail_ok) { echo ((running_script('install')) && (get_param_string('type', '') == 'ajax_db_details')) ? strip_html($error) : $error; return null; } critical_error('PASSON', $error); } if (!mysqli_select_db($db_link, $db_name)) { if ($db_user == 'root') { @mysqli_query($db_link, 'CREATE DATABASE IF NOT EXISTS ' . $db_name); } if (!mysqli_select_db($db_link, $db_name)) { $error = 'Could not connect to database (' . mysqli_error($db_link) . ')'; if ($fail_ok) { echo $error . "\n"; return null; } critical_error('PASSON', $error); //warn_exit(do_lang_tempcode('CONNECT_ERROR')); } } $this->last_select_db = [$db_link, $db_name]; $this->cache_db[$x] = $db_link; $init_queries = $this->get_init_queries(); foreach ($init_queries as $init_query) { @mysqli_query($db_link, $init_query); } global $SITE_INFO; $test = @mysqli_set_charset($db_link, $SITE_INFO['database_charset']); if ((!$test) && ($SITE_INFO['database_charset'] == 'utf8mb4')) { // Conflict between compiled-in MySQL client library and what the server supports $test = @mysqli_set_charset($db_link, 'utf8'); @mysqli_query($db_link, 'SET NAMES "' . addslashes('utf8mb4') . '"'); } return [$db_link, $db_name]; } /** * This function is a raw query executor. * This should rarely ever be used; other functions like query_select are available. Additionally, for complex queries, it is still better to use query_parameterised as it handles escaping. * * @param string $query The complete SQL query * @param mixed $connection The DB connection * @param ?integer $max The maximum number of rows to affect (null: no limit) * @param integer $start The start row to affect * @param boolean $fail_ok Whether to output an error on failure * @param boolean $get_insert_id Whether to get the autoincrement ID created for an insert query * @param boolean $save_as_volatile Whether we are saving as a 'volatile' file extension * @return ?mixed The results (null: no results), or the insert ID */ public function query(string $query, $connection, ?int $max = null, int $start = 0, bool $fail_ok = false, bool $get_insert_id = false, bool $save_as_volatile = false) { list($db_link, $db_name) = $connection; if (!$this->query_may_run($query, $connection, $get_insert_id)) { return null; } if (($this->last_select_db === null) || ($this->last_select_db[1] !== $db_name)) { mysqli_select_db($db_link, $db_name); $this->last_select_db = [$db_link, $db_name]; } if ($this->version === null) { $this->version = mysqli_get_server_version($db_link); } if ($this->version >= 80000) { $query = $this->fix_mysql8_query($query); // LEGACY: This can be removed once all user DBs are upgraded to MySQL 8 (as ALTER TABLE calls themselves are now MySQL 8 compatible by default } $this->apply_sql_limit_clause($query, $max, $start); $results = @mysqli_query($db_link, $query); if (($results === false) && ((!$fail_ok) || (strpos(mysqli_error($db_link), 'is marked as crashed and should be repaired') !== false))) { $err = mysqli_error($db_link); if (in_array($err, ['MySQL server has gone away', 'Lost connection to MySQL server during query']) && (!$this->reconnected_once)) { cms_ini_set('mysqli.reconnect', '1'); $this->reconnected_once = true; @mysqli_query($db_link, 'SELECT 1'); // Implicit reconnect $ret = $this->query($query, $connection, null/*already encoded*/, 0/*already encoded*/, $fail_ok, $get_insert_id); $this->reconnected_once = false; return $ret; } $this->handle_failed_query($query, $err, $connection); return null; } $sub = substr(ltrim($query), 0, 4); if (($results !== true) && (($sub === '(SEL') || ($sub === 'SELE') || ($sub === 'sele') || ($sub === 'CHEC') || ($sub === 'EXPL') || ($sub === 'REPA') || ($sub === 'DESC') || ($sub === 'SHOW')) && ($results !== false)) { return $this->get_query_rows($results, $query, $start); } if ($get_insert_id) { if (cms_strtoupper_ascii(substr(ltrim($query), 0, 7)) === 'UPDATE ') { return mysqli_affected_rows($db_link); } $ins = mysqli_insert_id($db_link); if ($ins === 0) { $table = substr($query, 12, strpos($query, ' ', 12) - 12); $rows = $this->query('SELECT MAX(id) AS x FROM ' . $table, $connection, 1, 0, false, false); return $rows[0]['x']; } return $ins; } return null; } /** * Get the rows returned from a SELECT query. * * @param resource $results The query result pointer * @param string $query The complete SQL query (useful for debugging) * @param integer $start Where to start reading from * @return array A list of row maps */ protected function get_query_rows($results, string $query, int $start) : array { $num_fields = mysqli_num_fields($results); $names = []; $types = []; $fields = mysqli_fetch_fields($results); foreach ($fields as $x => $field) { $field = mysqli_fetch_field($results); $names[$x] = $field->name; $types[$x] = $field->type; } $out = []; $newrow = []; while (($row = mysqli_fetch_row($results)) !== null) { $j = 0; foreach ($row as $v) { $name = $names[$j]; $type = $types[$j]; if (is_string($type)) { if (substr($type, 0, 3) == 'int') { $type = 'int'; } } if (($type === 1) || ($type === 2) || ($type === 3) || ($type === 8) || ($type === 9)) { // Integer field of some kind if ((($v === null)) || ($v === '')) { $newrow[$name] = null; } else { $newrow[$name] = intval($v); } } elseif (($type === 'real') || ($type === 4) || ($type === 5) || ($type === 246)) { // Decimal field of some kind if ((($v === null)) || ($v === '')) { $newrow[$name] = null; } else { $newrow[$name] = floatval($v); } } elseif ($type === 16) { // Bit field if ((strlen($v) === 1) && (ord($v[0]) <= 1)) { $newrow[$name] = ord($v); // 0/1 char for BIT field } else { $newrow[$name] = intval($v); // Int-as-string format } } else { $newrow[$name] = $v; } $j++; } $out[] = $newrow; } mysqli_free_result($results); return $out; } /** * Escape a string so it may be inserted into a query. If SQL statements are being built up and passed using db_query then it is essential that this is used for security reasons. Otherwise, the abstraction layer deals with the situation. * * @param string $string The string * @return string The escaped string */ public function escape_string(string $string) : string { if (preg_match('#[^a-zA-Z0-9\.]#', $string) === 0) { return $string; // No non-trivial characters } $string = fix_bad_unicode($string); if ($this->last_select_db === null) { return addslashes($string); } return mysqli_real_escape_string($this->last_select_db[0], $string); } }