900) {
$preview = substr($preview, 0, 900) . '...';
}
return $preview;
}
function znote_database_log_error(string $type, string $message, ?string $sql = null, int|string $code = 0): string {
$reference = znote_database_error_reference();
$parts = array(
'[ZnoteX DB]',
'ref=' . $reference,
'type=' . $type,
'code=' . (string)$code,
'context=' . znote_database_request_context(),
'message=' . preg_replace('/\s+/', ' ', trim($message)),
);
$preview = znote_database_sql_preview($sql);
if ($preview !== '') {
$parts[] = 'sql=' . $preview;
}
error_log(implode(' | ', $parts));
return $reference;
}
function znote_database_wait_screen(int $errorCode, string $errorMessage): void {
global $config;
if (PHP_SAPI === 'cli') {
die("Failed to connect to MySQL: (" . $errorCode . ") " . $errorMessage . PHP_EOL);
}
$reference = znote_database_log_error('connection', $errorMessage, null, $errorCode);
if (!headers_sent()) {
http_response_code(503);
header('Retry-After: 5');
header('Content-Type: text/html; charset=UTF-8');
}
$siteTitle = htmlspecialchars((string)($config['site_title'] ?? 'ZnoteX'), ENT_QUOTES, 'UTF-8');
$safeReference = htmlspecialchars($reference, ENT_QUOTES, 'UTF-8');
$showDetails = !empty($config['security']['show_database_errors']);
$safeDetails = $showDetails
? htmlspecialchars('MySQL ' . $errorCode . ': ' . $errorMessage, ENT_QUOTES, 'UTF-8')
: '';
?>
= $siteTitle ?> - Connecting
= $siteTitle ?>
The database connection is not ready yet.
Retrying automatically in 5 seconds
Reference: = $safeReference ?>
= $safeDetails ?>
set_charset('utf8mb4');
$connect->query("SET collation_connection = 'utf8mb4_general_ci'");
} catch (mysqli_sql_exception $e) {
znote_database_wait_screen($e->getCode(), $e->getMessage());
}
if ($connect->connect_errno) {
znote_database_wait_screen($connect->connect_errno, (string)$connect->connect_error);
}
if (!isset($aacQueries)) {
$aacQueries = 0;
}
if (!isset($accQueriesData)) {
$accQueriesData = [];
}
class ZnoteDatabase {
private mysqli $connection;
public function __construct(mysqli $connection) {
$this->connection = $connection;
}
public function connection(): mysqli {
return $this->connection;
}
public function fetchOne(string $sql, array $params = []): array|false {
$rows = $this->fetchAll($sql, $params);
return ($rows !== false && isset($rows[0])) ? $rows[0] : false;
}
public function fetchAll(string $sql, array $params = []): array|false {
$result = $this->query($sql, $params);
if (!($result instanceof mysqli_result)) {
return false;
}
$rows = [];
while ($row = $result->fetch_assoc()) {
$rows[] = $row;
}
$result->free();
return $rows ?: false;
}
public function execute(string $sql, array $params = []): bool {
$result = $this->query($sql, $params);
if ($result instanceof mysqli_result) {
$result->free();
}
return $result !== false;
}
public function insertId(): int|string {
return $this->connection->insert_id;
}
public function affectedRows(): int|string {
return $this->connection->affected_rows;
}
public function transaction(callable $callback): mixed {
try {
$this->connection->begin_transaction();
$result = $callback($this);
if ($result === false) {
$this->connection->rollback();
return false;
}
$this->connection->commit();
return $result;
} catch (Throwable $e) {
$this->connection->rollback();
znote_database_log_error('transaction', $e->getMessage(), null, (int)$e->getCode());
return false;
}
}
public function beginTransaction(): bool {
return $this->connection->begin_transaction();
}
public function commit(): bool {
return $this->connection->commit();
}
public function rollback(): bool {
return $this->connection->rollback();
}
public function rawFetchOne(string $sql): array|false {
$result = $this->rawQuery($sql);
if (!($result instanceof mysqli_result)) {
return false;
}
$row = $result->fetch_assoc();
$result->free();
return $row ?: false;
}
public function rawFetchAll(string $sql): array|false {
$result = $this->rawQuery($sql);
if (!($result instanceof mysqli_result)) {
return false;
}
$rows = [];
while ($row = $result->fetch_assoc()) {
$rows[] = $row;
}
$result->free();
return $rows ?: false;
}
public function rawExecute(string $sql): bool {
$result = $this->rawQuery($sql);
if ($result instanceof mysqli_result) {
$result->free();
}
return $result !== false;
}
private function query(string $sql, array $params = []): mysqli_result|bool {
$this->logQuery($sql, $params);
try {
if ($params === []) {
return $this->connection->query($sql);
}
$stmt = $this->connection->prepare($sql);
$this->bindParams($stmt, $params);
$stmt->execute();
$result = $this->statementResult($stmt);
$stmt->close();
return $result;
} catch (mysqli_sql_exception $e) {
znote_database_log_error('prepared-query', $e->getMessage(), $sql, (int)$e->getCode());
return false;
}
}
private function rawQuery(string $sql): mysqli_result|bool {
$this->logQuery($sql);
try {
return $this->connection->query($sql);
} catch (mysqli_sql_exception $e) {
znote_database_log_error('raw-query', $e->getMessage(), $sql, (int)$e->getCode());
return false;
}
}
private function logQuery(string $sql, array $params = []): void {
global $aacQueries, $accQueriesData;
$aacQueries++;
$accQueriesData[] = '[' . elapsedTime() . '] ' . $sql . ($params === [] ? '' : ' [prepared params: ' . count($params) . ']');
}
private function bindParams(mysqli_stmt $stmt, array $params): void {
$types = '';
$values = [];
foreach ($params as $param) {
if (is_int($param) || is_bool($param)) {
$types .= 'i';
$values[] = (int)$param;
} elseif (is_float($param)) {
$types .= 'd';
$values[] = $param;
} else {
$types .= 's';
$values[] = $param;
}
}
if ($types === '') {
return;
}
$refs = [];
foreach ($values as $key => &$value) {
$refs[$key] = &$value;
}
$stmt->bind_param($types, ...$refs);
}
private function statementResult(mysqli_stmt $stmt): mysqli_result|bool {
$result = $stmt->get_result();
if ($result instanceof mysqli_result) {
return $result;
}
return true;
}
}
function db(): ZnoteDatabase {
global $znoteDatabase, $connect;
if (!isset($znoteDatabase)) {
$znoteDatabase = new ZnoteDatabase($connect);
}
return $znoteDatabase;
}
function mysql_znote_escape_string($escapestr): string {
global $connect;
return mysqli_real_escape_string($connect, (string)($escapestr ?? ''));
}
function mysql_select_single(string $query): array|false {
return db()->rawFetchOne($query);
}
function mysql_select_multi(string $query): array|false {
return db()->rawFetchAll($query);
}
function voidQuery(string $query): bool {
return db()->rawExecute($query);
}
function mysql_update(string $query): bool { return voidQuery($query); }
function mysql_insert(string $query): bool { return voidQuery($query); }
function mysql_delete(string $query): bool { return voidQuery($query); }