query_count += 1; } /** * Replaces all string literals in the statement with placeholders. * * @param string SQL statement * @return string modified SQL statement */ protected static function replaceStrings($statement) { $count = mb_substr_count($statement, '"') + mb_substr_count($statement, "'") + mb_substr_count($statement, '\\'); // use fast preg_replace() variant if possible if ($count < 1000) { $result = preg_replace('/"(""|\\\\.|[^\\\\"]+)*"|\'(\'\'|\\\\.|[^\\\\\']+)*\'/s', '?', $statement); } if (!isset($result)) { // split string into parts at quotes and backslash $parts = preg_split('/([\\\\"\'])/', $statement, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); $result = ''; $quote_chr = null; for ($part = current($parts); $part !== false; $part = next($parts)) { // inside quotes, "" is ", '' is ' and \x is x if ($quote_chr !== NULL) { if ($part === $quote_chr) { $part = next($parts); if ($part !== $quote_chr) { // backtrack and terminate string prev($parts); $result .= '?'; $quote_chr = NULL; } } else if ($part === '\\') { // skip next part next($parts); } } else if ($part === "'" || $part === '"') { $quote_chr = $part; $saved_pos = key($parts); } else { $result .= $part; } } if ($quote_chr !== NULL) { // unterminated quote: copy to end of string $result .= implode(array_slice($parts, $saved_pos)); } } return $result; } /** * Quotes the given value in a form appropriate for the type. * If no explicit type is given, the value's PHP type is used. * * @param mixed $string PHP value to quote * @param ?int $type parameter type (e.g. PDO::PARAM_STR) * @return string|false quoted SQL string */ public function quote($string, $type = null): false|string { if (!isset($type)) { if (is_null($string)) { $type = PDO::PARAM_NULL; } else if (is_bool($string)) { $type = PDO::PARAM_BOOL; } else if (is_int($string)) { $type = PDO::PARAM_INT; } else if (is_array($string)) { $type = StudipPDO::PARAM_ARRAY; } else { $type = PDO::PARAM_STR; } } switch ($type) { case PDO::PARAM_NULL: return 'NULL'; case PDO::PARAM_BOOL: return $string ? '1' : '0'; case PDO::PARAM_INT: return (int) $string; case StudipPDO::PARAM_ARRAY: return is_array($string) && count($string) ? join(',', array_map([$this, 'quote'], $string)) : 'NULL'; case StudipPDO::PARAM_COLUMN: return preg_replace('/\\W/', '', $string); default: return parent::quote($string); } } /** * Executes an SQL statement and returns the number of affected rows. * * @param string $statement SQL statement */ public function exec(string $statement): false|int { $this->verify($statement); return parent::exec($statement); } /** * Executes an SQL statement, returning a result set as a statement object. * * @param string $statement SQL statement * @param int $fetch_mode fetch mode (optional) * @param mixed ...$fetch_args fetch mode parameters (see PDOStatement::setFetchMode) * @return object PDOStatement object */ #[ReturnTypeWillChange] public function query($statement, $fetch_mode = NULL, ...$fetch_args) { $this->verify($statement); if (isset($fetch_mode)) { $stmt = parent::query($statement, $fetch_mode, ...$fetch_args); } else { $stmt = parent::query($statement); } $studip_stmt = new StudipPDOStatement($this, $statement, []); $studip_stmt->setStatement($stmt); return $studip_stmt; } /** * Prepares a statement for execution and returns a statement object. * * @param string $statement SQL statement * @param array $driver_options * * @return StudipPDOStatement */ #[ReturnTypeWillChange] public function prepare($statement, $driver_options = []) { $this->verify($statement); return new StudipPDOStatement($this, $statement, $driver_options); } /** * This method is intended only for use by the StudipPDOStatement class. * * @param string SQL statement * @return object PDOStatement object */ public function prepareStatement($statement, $driver_options = []) { return parent::prepare($statement, $driver_options); } /** * Executes sql statement with given parameters, * returns number of affected rows, use only for INSERT,UPDATE etc * * @param string $statement SQL statement to execute * @param array $input_parameters parameters for statement * @return integer number of affected rows */ public function execute($statement, $input_parameters = null) { $st = $this->prepare($statement); $ok = $st->execute($input_parameters); if ($ok === true) { return $st->rowCount(); } return 0; } /** * Executes sql statement with given parameters, and fetch results * as sequential array, each row as associative array * optionally apply given callable on each row, with current row and key as parameter * * @param string $statement SQL statement to execute * @param array $input_parameters parameters for statement * @param callable $callable callable to be applied to each of the rows * @return array result set as array of assoc arrays */ public function fetchAll($statement, $input_parameters = null, $callable = null) { $st = $this->prepare($statement); $st->execute($input_parameters); if (is_callable($callable)) { $data = []; $st->setFetchMode(PDO::FETCH_ASSOC); foreach ($st as $key => $row) { $data[$key] = call_user_func($callable, $row, $key); } } else { $data = $st->fetchAll(PDO::FETCH_ASSOC); } return $data; } /** * Executes sql statement with given parameters, and fetch only * the values from first column as sequential array * optionally apply given callable on each row, with current value and key as parameter * * @see StudipPDOStatement::fetchFirst() * @param string $statement SQL statement to execute * @param array $input_parameters parameters for statement * @param callable $callable callable to be applied to each of the rows * @return array result set */ public function fetchFirst($statement, $input_parameters = null, $callable = null) { $st = $this->prepare($statement); $st->execute($input_parameters); $data = $st->fetchFirst(); if (is_callable($callable)) { foreach ($data as $key => $row) { $data[$key] = call_user_func($callable, $row, $key); } } return $data; } /** * Executes sql statement with given parameters, and fetch results * as associative array, first columns value is used as a key, the others are grouped * optionally apply given callable on each grouped row, with current row and key as parameter * if no callable is given, 'current' is used, to return the first entry of the grouped row * * @see StudipPDOStatement::fetchGrouped() * @param string $statement SQL statement to execute * @param array $input_parameters parameters for statement * @param callable $callable callable to be applied to each of the rows * @return array result set */ public function fetchGrouped($statement, $input_parameters = null, $callable = null) { $st = $this->prepare($statement); $st->execute($input_parameters); $data = $st->fetchGrouped(PDO::FETCH_ASSOC, is_null($callable) ? 'current' : null); if (is_callable($callable)) { foreach ($data as $key => $row) { $data[$key] = call_user_func($callable, $row, $key); } } return $data; } /** * Executes sql statement with given parameters, and fetch results * as associative array, first columns value is used as a key, the other one is grouped * use only when selecting 2 columns * optionally apply given callable on each grouped row, with current row and key as parameter * * @see StudipPDOStatement::fetchGroupedPairs() * @param string $statement SQL statement to execute * @param array $input_parameters parameters for statement * @param callable $callable callable to be applied to each of the rows * @return array result set */ public function fetchGroupedPairs($statement, $input_parameters = null, $callable = null) { $st = $this->prepare($statement); $st->execute($input_parameters); $data = $st->fetchGroupedPairs(); if (is_callable($callable)) { foreach ($data as $key => $row) { $data[$key] = call_user_func($callable, $row, $key); } } return $data; } /** * Executes sql statement with given parameters, and fetch results * as associative array, first columns value is used as a key, the other one as the value * use only when selecting 2 columns * optionally apply given callable on each grouped row, with current row and key as parameter * * @see StudipPDOStatement::fetchGroupedPairs() * @param string $statement SQL statement to execute * @param array $input_parameters parameters for statement * @param callable $callable callable to be applied to each of the rows * @return array result set */ public function fetchPairs($statement, $input_parameters = null, $callable = null) { $st = $this->prepare($statement); $st->execute($input_parameters); $data = $st->fetchPairs(); if (is_callable($callable)) { foreach ($data as $key => $row) { $data[$key] = call_user_func($callable, $row, $key); } } return $data; } /** * Executes sql statement with given parameters, and fetch only the first row * as associative array * * @see StudipPDOStatement::fetchOne() * @param string $statement SQL statement to execute * @param array $input_parameters parameters for statement * @return array first row of result set */ public function fetchOne($statement, $input_parameters = null) { $st = $this->prepare($statement); $st->execute($input_parameters); return $st->fetchOne(); } /** * Executes sql statement with given parameters, and fetch only the value of one column * third param denotes the column, zero indexed * * @param string $statement SQL statement to execute * @param array $input_parameters parameters for statement * @param integer $column number of column to fetch * @return string value of chosen column */ public function fetchColumn($statement, $input_parameters = null, $column = 0) { $st = $this->prepare($statement); $st->execute($input_parameters); return $st->fetchColumn($column); } /** * Determine if the connected database is a MariaDB database. * * @param string|null $min_version Optional check for a specific minimal * version * @return bool */ public function isMariaDB(?string $min_version = null): bool { $db_version = $this->getAttribute(\PDO::ATTR_SERVER_VERSION); if (stripos($db_version, 'MariaDB') === false) { return false; } if ($min_version === null) { return true; } return version_compare($db_version, $min_version) >= 0; } }