Файл: system/database.php
Строк: 182
<?php
/* Обновление игры под PHP 8.4 / PDO и усиление безопасности: by Метриум (Стэлп) */
declare(strict_types=1);
final class DBResult implements ArrayAccess, Countable, IteratorAggregate {
public array $rows;
public int $cursor = 0;
public function __construct(array $rows = []) { $this->rows = $rows; }
/* Legacy PHP 5 compatibility: some old game files used db_query(...)['field']
instead of fetching the first row. PDO returns an object, so expose the first
result row read-only while the old code is being progressively refactored.
Обновление: by Метриум (Стэлп). */
public function offsetExists(mixed $offset): bool {
return isset($this->rows[0]) && array_key_exists((string)$offset, $this->rows[0]);
}
public function offsetGet(mixed $offset): mixed {
return $this->rows[0][(string)$offset] ?? null;
}
public function offsetSet(mixed $offset, mixed $value): void {
throw new LogicException('DBResult is read-only');
}
public function offsetUnset(mixed $offset): void {
throw new LogicException('DBResult is read-only');
}
public function count(): int { return count($this->rows); }
public function getIterator(): Traversable { return new ArrayIterator($this->rows); }
}
$GLOBALS['__db_pdo'] = $GLOBALS['__db_pdo'] ?? null;
$GLOBALS['__db_last_error'] = '';
$GLOBALS['__db_last_insert_id'] = 0;
$GLOBALS['__db_query_count'] = 0;
$GLOBALS['__db_query_time'] = 0.0;
function db_config(): array {
static $cfg;
return $cfg ??= require __DIR__ . '/config.php';
}
function db_pdo(): PDO {
if ($GLOBALS['__db_pdo'] instanceof PDO) return $GLOBALS['__db_pdo'];
$c = db_config()['db'];
$dsn = sprintf('mysql:host=%s;dbname=%s;charset=%s', $c['host'], $c['name'], $c['charset']);
$pdo = new PDO($dsn, $c['user'], $c['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_PERSISTENT => false,
]);
$pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci");
$GLOBALS['__db_pdo']=$pdo;
return $pdo;
}
function db_connect(...$args): PDO|false { try { return db_pdo(); } catch (Throwable $e) { $GLOBALS['__db_last_error']=$e->getMessage(); return false; } }
function db_select_db(...$args): bool { return db_connect() !== false; }
function db_query(string $sql): DBResult|bool {
$started=microtime(true);
$GLOBALS['__db_query_count']=(int)($GLOBALS['__db_query_count'] ?? 0)+1;
try {
$stmt=db_pdo()->query($sql);
$GLOBALS['__db_last_insert_id']=(int)db_pdo()->lastInsertId();
if (preg_match('/^s*(SELECT|SHOW|DESCRIBE|EXPLAIN|WITH)b/i',$sql)) return new DBResult($stmt->fetchAll(PDO::FETCH_ASSOC));
return true;
} catch (Throwable $e) {
$GLOBALS['__db_last_error']=$e->getMessage();
$compactSql = substr((string)preg_replace('/s+/',' ', $sql), 0, 1000);
// Не пишем значения строковых литералов и секреты пользователей в журнал. by Метриум (Стэлп)
$safeSql = preg_replace("/'(?:''|[^'])*'/", "'[redacted]'", $compactSql) ?? '[query redacted]';
if (function_exists('app_log_write')) app_log_write('DB', 'Database query failed', ['sql'=>$safeSql,'type'=>get_class($e)]);
else error_log('[DB] Database query failed SQL='.$safeSql);
return false;
} finally {
$GLOBALS['__db_query_time']=(float)($GLOBALS['__db_query_time'] ?? 0)+(microtime(true)-$started);
}
}
function db_db_query(string $database,string $query): DBResult|bool { return db_query($query); }
function db_fetch_assoc(mixed $r): array|false { if(is_array($r)) return $r; if(!$r instanceof DBResult) return false; return $r->rows[$r->cursor++] ?? false; }
function db_fetch_array(mixed $r, int $mode=MYSQLI_BOTH): array|false {
$row=db_fetch_assoc($r); if($row===false) return false;
if($mode===MYSQLI_ASSOC) return $row;
if($mode===MYSQLI_NUM) return array_values($row);
$both=$row; foreach(array_values($row) as $i=>$v) $both[$i]=$v; return $both;
}
function db_num_rows(mixed $r): int { if($r instanceof DBResult) return count($r->rows); if(is_array($r)) return count($r); return 0; }
function db_result(mixed $r, int $row=0, int|string $field=0): mixed {
if(is_array($r)){ $x=$r; if(is_int($field)) return array_values($x)[$field] ?? false; return $x[$field] ?? false; }
if(!$r instanceof DBResult || !isset($r->rows[$row])) return false;
$x=$r->rows[$row]; if(is_int($field)) return array_values($x)[$field] ?? false; return $x[$field] ?? false;
}
function db_insert_id(): int { return (int)($GLOBALS['__db_last_insert_id'] ?: (db_connect() ? db_pdo()->lastInsertId() : 0)); }
function db_real_escape_string(string $s): string { $q=db_pdo()->quote($s); return substr($q,1,-1); }
function db_error(): string { return (string)($GLOBALS['__db_last_error'] ?? ''); }
function db_prepare(string $sql, array $params=[]): PDOStatement {
$started=microtime(true); $GLOBALS['__db_query_count']=(int)($GLOBALS['__db_query_count'] ?? 0)+1;
try {
$stmt=db_pdo()->prepare($sql);
$stmt->execute($params);
return $stmt;
} catch (Throwable $e) {
$GLOBALS['__db_last_error']=$e->getMessage();
$compactSql=substr((string)preg_replace('/s+/',' ',$sql),0,1000);
$safeSql=preg_replace("/'(?:''|[^'])*'/", "'[redacted]'", $compactSql) ?? '[query redacted]';
if(function_exists('app_log_write')) app_log_write('DB','Prepared query failed',['sql'=>$safeSql,'type'=>get_class($e)]);
throw $e;
} finally {
$GLOBALS['__db_query_time']=(float)($GLOBALS['__db_query_time'] ?? 0)+(microtime(true)-$started);
}
}
function db_fetch_one(string $sql,array $params=[]): ?array { $r=db_prepare($sql,$params)->fetch(PDO::FETCH_ASSOC); return $r===false?null:$r; }
function db_execute(string $sql,array $params=[]): bool { return db_prepare($sql,$params)->rowCount() >= 0; }