commit
975ab6190f
26 changed files with 1026 additions and 75 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,6 +2,7 @@
|
|||
/index.php
|
||||
/app/config/main.php
|
||||
/app/config/_*
|
||||
/app/config/db/*
|
||||
/app/cache/**/*.php
|
||||
/app/cache/**/*.lock
|
||||
/app/log/*
|
||||
|
|
|
@ -56,22 +56,46 @@ class DB extends PDO
|
|||
|
||||
public function __construct(string $dsn, string $username = null, string $password = null, array $options = [], string $prefix = '')
|
||||
{
|
||||
$type = \strstr($dsn, ':', true);
|
||||
$type = \strstr($dsn, ':', true);
|
||||
$typeU = \ucfirst($type);
|
||||
|
||||
if (
|
||||
! $type
|
||||
|| ! \in_array($type, PDO::getAvailableDrivers(), true)
|
||||
|| ! \is_file(__DIR__ . '/DB/' . \ucfirst($type) . '.php')
|
||||
|| ! \is_file(__DIR__ . "/DB/{$typeU}.php")
|
||||
) {
|
||||
throw new PDOException("Driver isn't found for '$type'");
|
||||
}
|
||||
$this->dbType = $type;
|
||||
|
||||
$statement = $typeU . 'Statement' . (\PHP_MAJOR_VERSION < 8 ? '7' : '');
|
||||
|
||||
if (\is_file(__DIR__ . "/DB/{$typeU}.php")) {
|
||||
$statement = 'ForkBB\\Core\\DB\\' . $statement;
|
||||
} else {
|
||||
$statement = DBStatement::class;
|
||||
}
|
||||
|
||||
if ('sqlite' === $type) {
|
||||
$dsn = \str_replace('!PATH!', \realpath(__DIR__ . '/../config/db') . '/', $dsn);
|
||||
}
|
||||
|
||||
$this->dbType = $type;
|
||||
$this->dbPrefix = $prefix;
|
||||
|
||||
if (isset($options['initSQLCommands'])) {
|
||||
$initSQLCommands = implode(';', $options['initSQLCommands']);
|
||||
|
||||
unset($options['initSQLCommands']);
|
||||
} else {
|
||||
$initSQLCommands = null;
|
||||
}
|
||||
|
||||
$options += [
|
||||
self::ATTR_DEFAULT_FETCH_MODE => self::FETCH_ASSOC,
|
||||
self::ATTR_EMULATE_PREPARES => false,
|
||||
self::ATTR_STRINGIFY_FETCHES => false,
|
||||
self::ATTR_ERRMODE => self::ERRMODE_EXCEPTION,
|
||||
self::ATTR_STATEMENT_CLASS => [DBStatement::class, [$this]],
|
||||
self::ATTR_STATEMENT_CLASS => [$statement, [$this]],
|
||||
];
|
||||
|
||||
$start = \microtime(true);
|
||||
|
@ -80,6 +104,10 @@ class DB extends PDO
|
|||
|
||||
$this->saveQuery('PDO::__construct()', \microtime(true) - $start, false);
|
||||
|
||||
if ($initSQLCommands) {
|
||||
$this->exec($initSQLCommands);
|
||||
}
|
||||
|
||||
$this->beginTransaction();
|
||||
}
|
||||
|
||||
|
@ -144,6 +172,7 @@ class DB extends PDO
|
|||
case 'i':
|
||||
case 'b':
|
||||
case 's':
|
||||
case 'f':
|
||||
$value = [1];
|
||||
break;
|
||||
default:
|
||||
|
@ -214,6 +243,14 @@ class DB extends PDO
|
|||
return $this->queries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает тип базы данных указанный в DSN
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->dbType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод для сохранения статистики по выполненному запросу
|
||||
*/
|
||||
|
|
88
app/Core/DB/AbstractSqliteStatement.php
Normal file
88
app/Core/DB/AbstractSqliteStatement.php
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of the ForkBB <https://github.com/forkbb>.
|
||||
*
|
||||
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
|
||||
* @license The MIT License (MIT)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ForkBB\Core\DB;
|
||||
|
||||
use ForkBB\Core\DB\AbstractStatement;
|
||||
use PDO;
|
||||
|
||||
abstract class AbstractSqliteStatement extends AbstractStatement
|
||||
{
|
||||
/**
|
||||
* https://github.com/php/php-src/blob/master/ext/pdo_sqlite/sqlite_statement.c
|
||||
*
|
||||
* SQLite:
|
||||
* native_type:
|
||||
* null - для значения NULL, а не типа столбца
|
||||
* integer - это INTEGER, NUMERIC(?), BOOLEAN // BOOLEAN тут как-то не к месту, его бы в отдельный тип
|
||||
* string - это TEXT
|
||||
* double - это REAL, NUMERIC(?) // NUMERIC может быть и double, и integer
|
||||
* sqlite:decl_type:
|
||||
* INTEGER
|
||||
* TEXT
|
||||
* REAL
|
||||
* NUMERIC
|
||||
* BOOLEAN
|
||||
* ... (это те типы, которые прописаны в CREATE TABLE и полученные после перекодировки из {driver}::bTypeRepl)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $nativeTypeRepl = [
|
||||
'integer' => self::INTEGER,
|
||||
'double' => self::FLOAT,
|
||||
];
|
||||
|
||||
public function getColumnsType(): array
|
||||
{
|
||||
if (isset($this->columnsType)) {
|
||||
return $this->columnsType;
|
||||
}
|
||||
|
||||
$this->columnsType = [];
|
||||
|
||||
$count = $this->columnCount();
|
||||
$i = 0;
|
||||
// $dbType = $this->db->getType();
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$meta = $this->getColumnMeta($i);
|
||||
$type = null;
|
||||
// $declType = $meta[$dbType . ':decl_type'] ?? null;
|
||||
$declType = $meta['sqlite:decl_type'] ?? null;
|
||||
|
||||
if (null === $declType) {
|
||||
$type = $this->nativeTypeRepl[$meta['native_type']] ?? null;
|
||||
} elseif (\preg_match('%INT%i', $declType)) {
|
||||
$type = self::INTEGER;
|
||||
} elseif (\preg_match('%BOOL%i', $declType)) {
|
||||
$type = self::BOOLEAN;
|
||||
// } elseif (\preg_match('%REAL|FLOA|DOUB|NUMERIC|DECIMAL%i', $declType)) {
|
||||
// $type = self::FLOAT;
|
||||
}
|
||||
|
||||
if ($type) {
|
||||
$this->columnsType[$i] = $type;
|
||||
|
||||
if (isset($meta['name'])) { // ????? проверка на тип содержимого? только строки, не числа?
|
||||
$this->columnsType[$meta['name']] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->columnsType;
|
||||
}
|
||||
|
||||
protected function convToBoolean(/* mixed */ $value): bool
|
||||
{
|
||||
return (bool) $value;
|
||||
}
|
||||
}
|
204
app/Core/DB/AbstractStatement.php
Normal file
204
app/Core/DB/AbstractStatement.php
Normal file
|
@ -0,0 +1,204 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of the ForkBB <https://github.com/forkbb>.
|
||||
*
|
||||
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
|
||||
* @license The MIT License (MIT)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ForkBB\Core\DB;
|
||||
|
||||
use ForkBB\Core\DBStatement;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use PDOException;
|
||||
|
||||
abstract class AbstractStatement extends DBStatement
|
||||
{
|
||||
/**
|
||||
* Типы столбцов полученные через getColumnMeta()
|
||||
* @var array
|
||||
*/
|
||||
protected $columnsType;
|
||||
|
||||
/**
|
||||
* Режим выборки установленный через setFetchMode()/fetchAll()
|
||||
* @var int
|
||||
*/
|
||||
protected $fetchMode;
|
||||
|
||||
/**
|
||||
* colno, class или object из setFetchMode()/fetchAll()
|
||||
* @var mixed
|
||||
*/
|
||||
protected $fetchArg;
|
||||
|
||||
/**
|
||||
* constructorArgs из setFetchMode()/fetchAll()
|
||||
* @var array
|
||||
*/
|
||||
protected $ctorArgs;
|
||||
|
||||
/**
|
||||
* Флаг успешного завершения fetch() для PDO::FETCH_COLUMN
|
||||
* @var bool
|
||||
*/
|
||||
protected $okFetchColumn;
|
||||
|
||||
abstract public function getColumnsType(): array;
|
||||
abstract protected function convToBoolean(/* mixed */ $value): bool;
|
||||
|
||||
protected function setFetchVars(int $mode, ...$args): void
|
||||
{
|
||||
$this->fetchMode = $mode;
|
||||
$this->fetchArg = null;
|
||||
$this->ctorArgs = null;
|
||||
|
||||
switch ($mode) {
|
||||
case PDO::FETCH_CLASS:
|
||||
$this->ctorArgs = $args[1] ?? null;
|
||||
case PDO::FETCH_INTO:
|
||||
$this->fetchArg = $args[0];
|
||||
break;
|
||||
case PDO::FETCH_COLUMN:
|
||||
$this->fetchArg = $args[0] ?? 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function dbSetFetchMode(int $mode, ...$args): bool
|
||||
{
|
||||
$this->setFetchVars($mode, ...$args);
|
||||
|
||||
return parent::setFetchMode($mode, ...$args);
|
||||
}
|
||||
|
||||
protected function dbFetch(int $mode = 0, int $orientation = PDO::FETCH_ORI_NEXT, int $offset = 0) /* : mixed */
|
||||
{
|
||||
$this->okFetchColumn = false;
|
||||
|
||||
if (0 === $mode) {
|
||||
$mode = $this->fetchMode ?? 0;
|
||||
$colNum = $this->fetchArg ?? 0;
|
||||
} else {
|
||||
$colNum = 0;
|
||||
}
|
||||
|
||||
$data = parent::fetch(
|
||||
PDO::FETCH_COLUMN === $mode ? PDO::FETCH_NUM : $mode,
|
||||
$orientation,
|
||||
$offset
|
||||
);
|
||||
|
||||
if (! \is_array($data)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$types = $this->getColumnsType();
|
||||
|
||||
foreach ($data as $key => &$value) {
|
||||
if (
|
||||
isset($types[$key])
|
||||
&& \is_scalar($value)
|
||||
) {
|
||||
switch ($types[$key]) {
|
||||
case self::INTEGER:
|
||||
$value += 0; // If the string is not a number, then Warning/Notice
|
||||
// It can return not an integer, but a float.
|
||||
break;
|
||||
case self::BOOLEAN:
|
||||
$value = $this->convToBoolean($value);
|
||||
break;
|
||||
case self::FLOAT:
|
||||
case self::STRING:
|
||||
break;
|
||||
default:
|
||||
throw new PDOException("Unknown field type: '{$types[$key]}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unset($value);
|
||||
|
||||
if (PDO::FETCH_COLUMN === $mode) {
|
||||
$this->okFetchColumn = true;
|
||||
$data = $data[$colNum];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function dbFetchAll(int $mode, ...$args): array
|
||||
{
|
||||
if (0 !== $mode) {
|
||||
$this->setFetchVars($mode, ...$args);
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
switch ($this->fetchMode) {
|
||||
case 0: /* PDO::FETCH_DEFAULT */
|
||||
case PDO::FETCH_BOTH:
|
||||
case PDO::FETCH_NUM:
|
||||
case PDO::FETCH_ASSOC:
|
||||
case PDO::FETCH_COLUMN:
|
||||
while (false !== ($data = $this->dbFetch()) || $this->okFetchColumn) {
|
||||
$result[] = $data;
|
||||
}
|
||||
|
||||
break;
|
||||
case PDO::FETCH_KEY_PAIR:
|
||||
if (2 !== $this->columnCount()) {
|
||||
throw new PDOException('General error: PDO::FETCH_KEY_PAIR fetch mode requires the result set to contain exactly 2 columns');
|
||||
}
|
||||
|
||||
while (false !== ($data = $this->dbFetch(PDO::FETCH_NUM))) {
|
||||
$result[$data[0]] = $data[1];
|
||||
}
|
||||
|
||||
break;
|
||||
case PDO::FETCH_UNIQUE:
|
||||
case PDO::FETCH_UNIQUE | PDO::FETCH_BOTH:
|
||||
case PDO::FETCH_UNIQUE | PDO::FETCH_NUM:
|
||||
case PDO::FETCH_UNIQUE | PDO::FETCH_ASSOC:
|
||||
$this->fetchMode ^= PDO::FETCH_UNIQUE;
|
||||
|
||||
while (false !== ($data = $this->dbFetch())) {
|
||||
$key = \array_shift($data);
|
||||
$result[$key] = $data;
|
||||
}
|
||||
|
||||
break;
|
||||
case PDO::FETCH_GROUP:
|
||||
case PDO::FETCH_GROUP | PDO::FETCH_BOTH:
|
||||
case PDO::FETCH_GROUP | PDO::FETCH_NUM:
|
||||
case PDO::FETCH_GROUP | PDO::FETCH_ASSOC:
|
||||
$this->fetchMode ^= PDO::FETCH_GROUP;
|
||||
|
||||
while (false !== ($data = $this->dbFetch())) {
|
||||
$key = \array_shift($data);
|
||||
|
||||
if (PDO::FETCH_BOTH === $this->fetchMode) {
|
||||
\array_shift($data);;
|
||||
}
|
||||
|
||||
if (! isset($result[$key])) {
|
||||
$result[$key] = [];
|
||||
}
|
||||
|
||||
$result[$key][] = $data;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new PDOException('AbstractStatement class does not support this type for fetchAll(): ' . $this->fetchMode);
|
||||
|
||||
return parent::fetchAll($mode, ...$args);
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
}
|
|
@ -175,11 +175,10 @@ class Mysql
|
|||
public function indexExists(string $table, string $index, bool $noPrefix = false): bool
|
||||
{
|
||||
$table = ($noPrefix ? '' : $this->dbPrefix) . $table;
|
||||
$index = 'PRIMARY' == $index ? $index : $table . '_' . $index;
|
||||
|
||||
$vars = [
|
||||
':tname' => $table,
|
||||
':index' => $index,
|
||||
':index' => 'PRIMARY' == $index ? $index : $table . '_' . $index,
|
||||
];
|
||||
$query = 'SELECT 1
|
||||
FROM INFORMATION_SCHEMA.STATISTICS
|
||||
|
@ -567,20 +566,22 @@ class Mysql
|
|||
public function getMap(): array
|
||||
{
|
||||
$vars = [
|
||||
str_replace('_', '\\_', $this->dbPrefix) . '%',
|
||||
':tname' => str_replace('_', '\\_', $this->dbPrefix) . '%',
|
||||
];
|
||||
$query = 'SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME LIKE ?s';
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME LIKE ?s:tname
|
||||
ORDER BY TABLE_NAME';
|
||||
|
||||
$stmt = $this->db->query($query, $vars);
|
||||
$result = [];
|
||||
$table = null;
|
||||
$prfLen = \strlen($this->dbPrefix);
|
||||
|
||||
while ($row = $stmt->fetch()) {
|
||||
if ($table !== $row['TABLE_NAME']) {
|
||||
$table = $row['TABLE_NAME'];
|
||||
$tableNoPref = \substr($table, \strlen($this->dbPrefix));
|
||||
$tableNoPref = \substr($table, $prfLen);
|
||||
$result[$tableNoPref] = [];
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ class Pgsql
|
|||
'%^BIGINT(?:\s*\(\d+\))?(?:\s*UNSIGNED)?$%i' => 'BIGINT',
|
||||
'%^(?:TINY|MEDIUM|LONG)TEXT$%i' => 'TEXT',
|
||||
'%^DOUBLE(?:\s+PRECISION)?(?:\s*\([\d,]+\))?(?:\s*UNSIGNED)?$%i' => 'DOUBLE PRECISION',
|
||||
'%^FLOAT(?:\s*\([\d,]+\))?(?:\s*UNSIGNED)?$%i' => 'REAL',
|
||||
'%^(?:FLOAT|REAL)(?:\s*\([\d,]+\))?(?:\s*UNSIGNED)?$%i' => 'REAL',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -189,13 +189,12 @@ class Pgsql
|
|||
public function indexExists(string $table, string $index, bool $noPrefix = false): bool
|
||||
{
|
||||
$table = ($noPrefix ? '' : $this->dbPrefix) . $table;
|
||||
$index = $table . '_' . ('PRIMARY' === $index ? 'pkey' : $index);
|
||||
|
||||
$vars = [
|
||||
':schema' => 'public',
|
||||
':tname' => $table,
|
||||
':ttype' => 'r',
|
||||
':iname' => $index,
|
||||
':iname' => $table . '_' . ('PRIMARY' === $index ? 'pkey' : $index),
|
||||
':itype' => 'i',
|
||||
];
|
||||
$query = 'SELECT 1
|
||||
|
@ -525,16 +524,18 @@ class Pgsql
|
|||
];
|
||||
$query = 'SELECT table_name, column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_catalog = current_database() AND table_schema = ?s:schema AND table_name LIKE ?s:tname';
|
||||
WHERE table_catalog = current_database() AND table_schema = ?s:schema AND table_name LIKE ?s:tname
|
||||
ORDER BY table_name';
|
||||
|
||||
$stmt = $this->db->query($query, $vars);
|
||||
$result = [];
|
||||
$table = null;
|
||||
$prfLen = \strlen($this->dbPrefix);
|
||||
|
||||
while ($row = $stmt->fetch()) {
|
||||
if ($table !== $row['table_name']) {
|
||||
$table = $row['table_name'];
|
||||
$tableNoPref = \substr($table, \strlen($this->dbPrefix));
|
||||
$tableNoPref = \substr($table, $prfLen);
|
||||
$result[$tableNoPref] = [];
|
||||
}
|
||||
|
||||
|
|
501
app/Core/DB/Sqlite.php
Normal file
501
app/Core/DB/Sqlite.php
Normal file
|
@ -0,0 +1,501 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of the ForkBB <https://github.com/forkbb>.
|
||||
*
|
||||
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
|
||||
* @license The MIT License (MIT)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ForkBB\Core\DB;
|
||||
|
||||
use ForkBB\Core\DB;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use PDOException;
|
||||
|
||||
class Sqlite
|
||||
{
|
||||
/**
|
||||
* @var DB
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* Префикс для таблиц базы
|
||||
* @var string
|
||||
*/
|
||||
protected $dbPrefix;
|
||||
|
||||
/**
|
||||
* Массив замены типов полей таблицы
|
||||
* @var array
|
||||
*/
|
||||
protected $dbTypeRepl = [
|
||||
'%^.*?INT.*$%i' => 'INTEGER',
|
||||
'%^.*?(?:CHAR|CLOB|TEXT).*$%i' => 'TEXT',
|
||||
'%^.*?BLOB.*$%i' => 'BLOB',
|
||||
'%^.*?(?:REAL|FLOA|DOUB).*$%i' => 'REAL',
|
||||
'%^.*?(?:NUMERIC|DECIMAL).*$%i' => 'NUMERIC',
|
||||
'%^.*?BOOL.*$%i' => 'BOOLEAN', // ???? не соответствует SQLite
|
||||
'%^SERIAL$%i' => 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL',
|
||||
];
|
||||
|
||||
/**
|
||||
* Подстановка типов полей для карты БД
|
||||
* @var array
|
||||
*/
|
||||
protected $types = [
|
||||
'boolean' => 'b',
|
||||
'integer' => 'i',
|
||||
'real' => 'f',
|
||||
'numeric' => 'f',
|
||||
];
|
||||
|
||||
public function __construct(DB $db, string $prefix)
|
||||
{
|
||||
$this->db = $db;
|
||||
|
||||
$this->testStr($prefix);
|
||||
|
||||
$this->dbPrefix = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Перехват неизвестных методов
|
||||
*/
|
||||
public function __call(string $name, array $args)
|
||||
{
|
||||
throw new PDOException("Method '{$name}' not found in DB driver.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет строку на допустимые символы
|
||||
*/
|
||||
protected function testStr(string $str): void
|
||||
{
|
||||
if (\preg_match('%[^a-zA-Z0-9_]%', $str)) {
|
||||
throw new PDOException("Name '{$str}' have bad characters.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Операции над полями индексов: проверка, замена
|
||||
*/
|
||||
protected function replIdxs(array $arr): string
|
||||
{
|
||||
foreach ($arr as &$value) {
|
||||
if (\preg_match('%^(.*)\s*(\(\d+\))$%', $value, $matches)) {
|
||||
$this->testStr($matches[1]);
|
||||
|
||||
$value = "\"{$matches[1]}\""; // {$matches[2]}
|
||||
} else {
|
||||
$this->testStr($value);
|
||||
|
||||
$value = "\"{$value}\"";
|
||||
}
|
||||
}
|
||||
|
||||
unset($value);
|
||||
|
||||
return \implode(',', $arr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Замена типа поля в соответствии с dbTypeRepl
|
||||
*/
|
||||
protected function replType(string $type): string
|
||||
{
|
||||
return \preg_replace(\array_keys($this->dbTypeRepl), \array_values($this->dbTypeRepl), $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Конвертирует данные в строку для DEFAULT
|
||||
*/
|
||||
protected function convToStr(/* mixed */ $data): string
|
||||
{
|
||||
if (\is_string($data)) {
|
||||
return $this->db->quote($data);
|
||||
} elseif (\is_numeric($data)) {
|
||||
return (string) $data;
|
||||
} elseif (\is_bool($data)) {
|
||||
return $data ? 'true' : 'false';
|
||||
} else {
|
||||
throw new PDOException('Invalid data type for DEFAULT.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет наличие таблицы в базе
|
||||
*/
|
||||
public function tableExists(string $table, bool $noPrefix = false): bool
|
||||
{
|
||||
$vars = [
|
||||
':tname' => ($noPrefix ? '' : $this->dbPrefix) . $table,
|
||||
':ttype' => 'table',
|
||||
];
|
||||
$query = 'SELECT 1 FROM sqlite_master WHERE tbl_name=?s:tname AND type=?s:ttype';
|
||||
|
||||
$stmt = $this->db->query($query, $vars);
|
||||
$result = $stmt->fetch();
|
||||
|
||||
$stmt->closeCursor();
|
||||
|
||||
return ! empty($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет наличие поля в таблице
|
||||
*/
|
||||
public function fieldExists(string $table, string $field, bool $noPrefix = false): bool
|
||||
{
|
||||
$this->testStr($table);
|
||||
|
||||
$table = ($noPrefix ? '' : $this->dbPrefix) . $table;
|
||||
$stmt = $this->db->query("PRAGMA table_info({$table})");
|
||||
|
||||
while ($row = $stmt->fetch()) {
|
||||
if ($field === $row['name']) {
|
||||
$stmt->closeCursor();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет наличие индекса в таблице
|
||||
*/
|
||||
public function indexExists(string $table, string $index, bool $noPrefix = false): bool
|
||||
{
|
||||
$table = ($noPrefix ? '' : $this->dbPrefix) . $table;
|
||||
|
||||
$vars = [
|
||||
':tname' => $table,
|
||||
':iname' => $table . '_' . $index, // ???? PRIMARY KEY искать нужно не в sqlite_master!
|
||||
':itype' => 'index',
|
||||
];
|
||||
$query = 'SELECT 1 FROM sqlite_master WHERE name=?s:iname AND tbl_name=?s:tname AND type=?s:itype';
|
||||
|
||||
$stmt = $this->db->query($query, $vars);
|
||||
$result = $stmt->fetch();
|
||||
|
||||
$stmt->closeCursor();
|
||||
|
||||
return ! empty($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Создает таблицу
|
||||
*/
|
||||
public function createTable(string $table, array $schema, bool $noPrefix = false): bool
|
||||
{
|
||||
$this->testStr($table);
|
||||
|
||||
$prKey = true;
|
||||
$table = ($noPrefix ? '' : $this->dbPrefix) . $table;
|
||||
$query = "CREATE TABLE IF NOT EXISTS \"{$table}\" (";
|
||||
|
||||
foreach ($schema['FIELDS'] as $field => $data) {
|
||||
$this->testStr($field);
|
||||
// имя и тип
|
||||
$query .= "\"{$field}\" " . $this->replType($data[0]);
|
||||
|
||||
if ('SERIAL' === \strtoupper($data[0])) {
|
||||
$prKey = false;
|
||||
} else {
|
||||
// сравнение
|
||||
if (\preg_match('%^(?:CHAR|VARCHAR|TINYTEXT|TEXT|MEDIUMTEXT|LONGTEXT|ENUM|SET)%i', $data[0])) {
|
||||
$query .= ' COLLATE ';
|
||||
|
||||
if (
|
||||
isset($data[3])
|
||||
&& \is_string($data[3])
|
||||
&& \preg_match('%bin%i', $data[3])
|
||||
) {
|
||||
$query .= 'BINARY';
|
||||
} else {
|
||||
$query .= 'NOCASE';
|
||||
}
|
||||
}
|
||||
// не NULL
|
||||
if (empty($data[1])) {
|
||||
$query .= ' NOT NULL';
|
||||
}
|
||||
// значение по умолчанию
|
||||
if (isset($data[2])) {
|
||||
$query .= ' DEFAULT ' . $this->convToStr($data[2]);
|
||||
}
|
||||
}
|
||||
|
||||
$query .= ', ';
|
||||
}
|
||||
|
||||
if ($prKey && isset($schema['PRIMARY KEY'])) { // если не было поля с типом SERIAL
|
||||
$query .= 'PRIMARY KEY (' . $this->replIdxs($schema['PRIMARY KEY']) . '), ';
|
||||
}
|
||||
|
||||
$query = \rtrim($query, ', ') . ")";
|
||||
$result = false !== $this->db->exec($query);
|
||||
|
||||
// вынесено отдельно для сохранения имен индексов
|
||||
if ($result && isset($schema['UNIQUE KEYS'])) {
|
||||
foreach ($schema['UNIQUE KEYS'] as $key => $fields) {
|
||||
$result = $result && $this->addIndex($table, $key, $fields, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
if ($result && isset($schema['INDEXES'])) {
|
||||
foreach ($schema['INDEXES'] as $index => $fields) {
|
||||
$result = $result && $this->addIndex($table, $index, $fields, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаляет таблицу
|
||||
*/
|
||||
public function dropTable(string $table, bool $noPrefix = false): bool
|
||||
{
|
||||
$this->testStr($table);
|
||||
|
||||
$table = ($noPrefix ? '' : $this->dbPrefix) . $table;
|
||||
|
||||
return false !== $this->db->exec("DROP TABLE IF EXISTS \"{$table}\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* Переименовывает таблицу
|
||||
*/
|
||||
public function renameTable(string $old, string $new, bool $noPrefix = false): bool
|
||||
{
|
||||
$this->testStr($old);
|
||||
$this->testStr($new);
|
||||
|
||||
if (
|
||||
$this->tableExists($new, $noPrefix)
|
||||
&& ! $this->tableExists($old, $noPrefix)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$old = ($noPrefix ? '' : $this->dbPrefix) . $old;
|
||||
$new = ($noPrefix ? '' : $this->dbPrefix) . $new;
|
||||
|
||||
return false !== $this->db->exec("ALTER TABLE \"{$old}\" RENAME TO \"{$new}\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляет поле в таблицу // ???? нет COLLATE
|
||||
*/
|
||||
public function addField(string $table, string $field, string $type, bool $allowNull, /* mixed */ $default = null, string $after = null, bool $noPrefix = false): bool
|
||||
{
|
||||
$this->testStr($table);
|
||||
$this->testStr($field);
|
||||
|
||||
if ($this->fieldExists($table, $field, $noPrefix)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$table = ($noPrefix ? '' : $this->dbPrefix) . $table;
|
||||
$query = "ALTER TABLE \"{$table}\" ADD COLUMN \"{$field}\" " . $this->replType($type);
|
||||
|
||||
if ('SERIAL' !== \strtoupper($type)) {
|
||||
if (! $allowNull) {
|
||||
$query .= ' NOT NULL';
|
||||
}
|
||||
|
||||
if (null !== $default) {
|
||||
$query .= ' DEFAULT ' . $this->convToStr($default);
|
||||
}
|
||||
}
|
||||
|
||||
return false !== $this->db->exec($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Модифицирует поле в таблице
|
||||
*/
|
||||
public function alterField(string $table, string $field, string $type, bool $allowNull, /* mixed */ $default = null, string $after = null, bool $noPrefix = false): bool
|
||||
{
|
||||
$this->testStr($table);
|
||||
$this->testStr($field);
|
||||
|
||||
$table = ($noPrefix ? '' : $this->dbPrefix) . $table;
|
||||
|
||||
return true; // ???????????????????????????????????????
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаляет поле из таблицы
|
||||
*/
|
||||
public function dropField(string $table, string $field, bool $noPrefix = false): bool
|
||||
{
|
||||
$this->testStr($table);
|
||||
$this->testStr($field);
|
||||
|
||||
if (! $this->fieldExists($table, $field, $noPrefix)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$table = ($noPrefix ? '' : $this->dbPrefix) . $table;
|
||||
|
||||
return false !== $this->db->exec("ALTER TABLE \"{$table}\" DROP COLUMN \"{$field}\""); // ???? add 2021-03-12 (3.35.0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Переименование поля в таблице
|
||||
*/
|
||||
public function renameField(string $table, string $old, string $new, bool $noPrefix = false): bool
|
||||
{
|
||||
$this->testStr($table);
|
||||
$this->testStr($old);
|
||||
$this->testStr($new);
|
||||
|
||||
if (
|
||||
$this->fieldExists($table, $new, $noPrefix)
|
||||
&& ! $this->fieldExists($table, $old, $noPrefix)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$table = ($noPrefix ? '' : $this->dbPrefix) . $table;
|
||||
|
||||
return false !== $this->db->exec("ALTER TABLE \"{$table}\" RENAME COLUMN \"{$old}\" TO \"{$new}\""); // ???? add 2018-09-15 (3.25.0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляет индекс в таблицу
|
||||
*/
|
||||
public function addIndex(string $table, string $index, array $fields, bool $unique = false, bool $noPrefix = false): bool
|
||||
{
|
||||
$this->testStr($table);
|
||||
|
||||
if ($this->indexExists($table, $index, $noPrefix)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$table = ($noPrefix ? '' : $this->dbPrefix) . $table;
|
||||
|
||||
if ('PRIMARY' === $index) {
|
||||
// ?????
|
||||
} else {
|
||||
$index = $table . '_' . $index;
|
||||
|
||||
$this->testStr($index);
|
||||
|
||||
$unique = $unique ? 'UNIQUE' : '';
|
||||
$query = "CREATE {$unique} INDEX \"{$index}\" ON \"{$table}\" (" . $this->replIdxs($fields) . ')';
|
||||
}
|
||||
|
||||
return false !== $this->db->exec($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаляет индекс из таблицы
|
||||
*/
|
||||
public function dropIndex(string $table, string $index, bool $noPrefix = false): bool
|
||||
{
|
||||
$this->testStr($table);
|
||||
|
||||
if (! $this->indexExists($table, $index, $noPrefix)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$table = ($noPrefix ? '' : $this->dbPrefix) . $table;
|
||||
$index = $table . '_' . ('PRIMARY' === $index ? 'pkey' : $index);
|
||||
|
||||
$this->testStr($index);
|
||||
|
||||
return false !== $this->db->exec("DROP INDEX \"{$index}\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* Очищает таблицу
|
||||
*/
|
||||
public function truncateTable(string $table, bool $noPrefix = false): bool
|
||||
{
|
||||
$this->testStr($table);
|
||||
|
||||
$table = ($noPrefix ? '' : $this->dbPrefix) . $table;
|
||||
|
||||
if (false !== $this->db->exec("DELETE FROM \"{$table}\"")) {
|
||||
$vars = [
|
||||
':tname' => $table,
|
||||
];
|
||||
$query = 'DELETE FROM SQLITE_SEQUENCE WHERE name=?s:tname';
|
||||
|
||||
return false !== $this->db->exec($query, $vars);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает статистику
|
||||
*/
|
||||
public function statistics(): array
|
||||
{
|
||||
$vars = [
|
||||
':tname' => \str_replace('_', '\\_', $this->dbPrefix) . '%',
|
||||
':ttype' => 'table',
|
||||
];
|
||||
$query = 'SELECT COUNT(*) FROM sqlite_master WHERE tbl_name LIKE ?s:tname ESCAPE \'\\\' AND type=?s:ttype';
|
||||
|
||||
$tables = $this->db->query($query, $vars)->fetchColumn();
|
||||
|
||||
$records = 0;
|
||||
$size = (int) $this->db->query('PRAGMA page_count;')->fetchColumn();
|
||||
$size *= (int) $this->db->query('PRAGMA page_size;')->fetchColumn();
|
||||
|
||||
return [
|
||||
'db' => 'SQLite (PDO) v.' . $this->db->getAttribute(PDO::ATTR_SERVER_VERSION),
|
||||
'tables' => (string) $tables,
|
||||
'records' => $records,
|
||||
'size' => $size,
|
||||
# 'server info' => $this->db->getAttribute(PDO::ATTR_SERVER_INFO),
|
||||
'encoding' => $this->db->query('PRAGMA encoding;')->fetchColumn(),
|
||||
'journal_mode' => $this->db->query('PRAGMA journal_mode;')->fetchColumn(),
|
||||
'synchronous' => $this->db->query('PRAGMA synchronous;')->fetchColumn(),
|
||||
'busy_timeout' => $this->db->query('PRAGMA busy_timeout;')->fetchColumn(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Формирует карту базы данных
|
||||
*/
|
||||
public function getMap(): array
|
||||
{
|
||||
$vars = [
|
||||
':tname' => \str_replace('_', '\\_', $this->dbPrefix) . '%',
|
||||
];
|
||||
$query = 'SELECT m.name AS table_name, p.name AS column_name, p.type AS data_type
|
||||
FROM sqlite_master AS m
|
||||
INNER JOIN pragma_table_info(m.name) AS p
|
||||
WHERE table_name LIKE ?s:tname ESCAPE \'\\\'
|
||||
ORDER BY m.name, p.cid';
|
||||
|
||||
$stmt = $this->db->query($query, $vars);
|
||||
$result = [];
|
||||
$table = null;
|
||||
$prfLen = \strlen($this->dbPrefix);
|
||||
|
||||
while ($row = $stmt->fetch()) {
|
||||
if ($table !== $row['table_name']) {
|
||||
$table = $row['table_name'];
|
||||
$tableNoPref = \substr($table, $prfLen);
|
||||
$result[$tableNoPref] = [];
|
||||
}
|
||||
|
||||
$type = \strtolower($row['data_type']);
|
||||
$result[$tableNoPref][$row['column_name']] = $this->types[$type] ?? 's';
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
35
app/Core/DB/SqliteStatement.php
Normal file
35
app/Core/DB/SqliteStatement.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of the ForkBB <https://github.com/forkbb>.
|
||||
*
|
||||
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
|
||||
* @license The MIT License (MIT)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ForkBB\Core\DB;
|
||||
|
||||
use ForkBB\Core\DB\AbstractSqliteStatement;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* For PHP 8
|
||||
*/
|
||||
class SqliteStatement extends AbstractSqliteStatement
|
||||
{
|
||||
public function fetch(int $mode = 0 /* PDO::FETCH_DEFAULT */, int $orientation = PDO::FETCH_ORI_NEXT, int $offset = 0): mixed
|
||||
{
|
||||
return $this->dbFetch($mode, $orientation, $offset);
|
||||
}
|
||||
|
||||
public function fetchAll(int $mode = 0 /* PDO::FETCH_DEFAULT */, ...$args): array
|
||||
{
|
||||
return $this->dbFetchAll($mode, ...$args);
|
||||
}
|
||||
|
||||
public function setFetchMode(int $mode, ...$args): bool
|
||||
{
|
||||
return $this->dbSetFetchMode($mode, ...$args);
|
||||
}
|
||||
}
|
59
app/Core/DB/SqliteStatement7.php
Normal file
59
app/Core/DB/SqliteStatement7.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of the ForkBB <https://github.com/forkbb>.
|
||||
*
|
||||
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
|
||||
* @license The MIT License (MIT)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ForkBB\Core\DB;
|
||||
|
||||
use ForkBB\Core\DB\AbstractSqliteStatement;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* For PHP 7
|
||||
*/
|
||||
class SqliteStatement7 extends AbstractSqliteStatement
|
||||
{
|
||||
public function fetch($mode = null, $orientation = null, $offset = null)
|
||||
{
|
||||
$mode = $mode ?? 0;
|
||||
$orientation = $orientation ?? PDO::FETCH_ORI_NEXT;
|
||||
$offset = $offset ?? 0;
|
||||
|
||||
return $this->dbFetch($mode, $orientation, $offset);
|
||||
}
|
||||
|
||||
public function fetchAll($mode = null, $fetchArg = null, $ctorArgs = null)
|
||||
{
|
||||
$mode = $mode ?? 0;
|
||||
$args = $this->returnArgs($fetchArg, $ctorArgs);
|
||||
|
||||
return $this->dbFetchAll($mode, ...$args);
|
||||
}
|
||||
|
||||
public function setFetchMode($mode, $fetchArg = null, $ctorArgs = null): bool
|
||||
{
|
||||
$args = $this->returnArgs($fetchArg, $ctorArgs);
|
||||
|
||||
return $this->dbSetFetchMode($mode, ...$args);
|
||||
}
|
||||
|
||||
protected function returnArgs($fetchArg, $ctorArgs): array
|
||||
{
|
||||
$args = [];
|
||||
|
||||
if (isset($fetchArg)) {
|
||||
$args[] = $fetchArg;
|
||||
|
||||
if (isset($ctorArgs)) {
|
||||
$args[] = $ctorArgs;
|
||||
}
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
}
|
|
@ -16,6 +16,11 @@ use PDOException;
|
|||
|
||||
class DBStatement extends PDOStatement
|
||||
{
|
||||
const BOOLEAN = 'b';
|
||||
const FLOAT = 'f';
|
||||
const INTEGER = 'i';
|
||||
const STRING = 's';
|
||||
|
||||
/**
|
||||
* Префикс для таблиц базы
|
||||
* @var PDO
|
||||
|
@ -33,12 +38,13 @@ class DBStatement extends PDOStatement
|
|||
* @var array
|
||||
*/
|
||||
protected $types = [
|
||||
's' => PDO::PARAM_STR,
|
||||
'i' => PDO::PARAM_INT,
|
||||
'b' => PDO::PARAM_BOOL,
|
||||
'f' => PDO::PARAM_STR,
|
||||
'i' => PDO::PARAM_INT,
|
||||
's' => PDO::PARAM_STR,
|
||||
'a' => PDO::PARAM_STR,
|
||||
'as' => PDO::PARAM_STR,
|
||||
'ai' => PDO::PARAM_INT,
|
||||
'as' => PDO::PARAM_STR,
|
||||
];
|
||||
|
||||
protected function __construct(PDO $db)
|
||||
|
|
|
@ -168,7 +168,10 @@ class ErrorHandler
|
|||
$useErrLog = true;
|
||||
|
||||
try {
|
||||
if (! $this->c->Log instanceof NullLogger) {
|
||||
if (
|
||||
$this->c instanceof Container
|
||||
&& ! $this->c->Log instanceof NullLogger
|
||||
) {
|
||||
$context = [];
|
||||
$method = $this->type[$error['type']][1] ?? $this->type[0][1];
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ class CalcStat extends Method
|
|||
FROM ::topics AS t
|
||||
WHERE t.forum_id=?i:fid AND t.moved_to!=0';
|
||||
|
||||
$moved = $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
$moved = (int) $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
|
||||
$query = 'SELECT COUNT(t.id) as num_topics, SUM(t.num_replies) as num_replies
|
||||
FROM ::topics AS t
|
||||
|
|
|
@ -53,7 +53,7 @@ class CalcStat extends Method
|
|||
FROM ::pm_posts AS pp
|
||||
WHERE pp.topic_id=?i:tid';
|
||||
|
||||
$this->model->num_replies = $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
$this->model->num_replies = (int) $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
|
||||
return $this->model;
|
||||
}
|
||||
|
|
|
@ -238,8 +238,8 @@ class PPost extends DataModel
|
|||
ORDER BY pp.id DESC
|
||||
LIMIT 1";
|
||||
|
||||
$id = $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
$id = (int) $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
|
||||
return empty($id) ? null : $id;
|
||||
return $id ?: null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -176,9 +176,7 @@ class PTopic extends DataModel
|
|||
FROM ::pm_posts AS pp
|
||||
WHERE pp.topic_id=?i:tid AND pp.posted>?i:visit';
|
||||
|
||||
$pid = $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
|
||||
return $pid ?: 0;
|
||||
return (int) $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
}
|
||||
|
||||
protected function setsender(User $user): void
|
||||
|
|
|
@ -20,7 +20,9 @@ use function \ForkBB\__;
|
|||
|
||||
class Install extends Admin
|
||||
{
|
||||
const PHP_MIN = '7.3.0';
|
||||
const PHP_MIN = '7.3.0';
|
||||
const MYSQL_MIN = '5.5.3';
|
||||
const SQLITE_MIN = '3.25.0';
|
||||
|
||||
const JSON_OPTIONS = \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR;
|
||||
|
||||
|
@ -109,6 +111,7 @@ class Install extends Admin
|
|||
// доступность папок на запись
|
||||
$folders = [
|
||||
$this->c->DIR_APP . '/config',
|
||||
$this->c->DIR_APP . '/config/db',
|
||||
$this->c->DIR_CACHE,
|
||||
$this->c->DIR_PUBLIC . '/img/avatars',
|
||||
];
|
||||
|
@ -159,7 +162,7 @@ class Install extends Admin
|
|||
'dbname' => 'required|string:trim',
|
||||
'dbuser' => 'string:trim',
|
||||
'dbpass' => 'string:trim',
|
||||
'dbprefix' => 'required|string:trim|max:40|check_prefix',
|
||||
'dbprefix' => 'required|string:trim|min:1|max:40|check_prefix',
|
||||
'username' => 'required|string:trim|min:2|max:25',
|
||||
'password' => 'required|string|min:16|max:100000|password',
|
||||
'email' => 'required|string:trim|email',
|
||||
|
@ -472,11 +475,13 @@ class Install extends Admin
|
|||
*/
|
||||
public function vCheckHost(Validator $v, $dbhost)
|
||||
{
|
||||
$this->c->DB_USERNAME = $v->dbuser;
|
||||
$this->c->DB_PASSWORD = $v->dbpass;
|
||||
$this->c->DB_PREFIX = $v->dbprefix;
|
||||
$dbtype = $v->dbtype;
|
||||
$dbname = $v->dbname;
|
||||
$this->c->DB_USERNAME = $v->dbuser;
|
||||
$this->c->DB_PASSWORD = $v->dbpass;
|
||||
$this->c->DB_OPTIONS = [];
|
||||
$this->c->DB_OPTS_AS_STR = '';
|
||||
$this->c->DB_PREFIX = $v->dbprefix;
|
||||
$dbtype = $v->dbtype;
|
||||
$dbname = $v->dbname;
|
||||
|
||||
// есть ошибки, ни чего не проверяем
|
||||
if (! empty($v->getErrors())) {
|
||||
|
@ -500,6 +505,15 @@ class Install extends Admin
|
|||
|
||||
break;
|
||||
case 'sqlite':
|
||||
$this->c->DB_DSN = "sqlite:!PATH!{$dbname}";
|
||||
$this->c->DB_OPTS_AS_STR = '\\PDO::ATTR_TIMEOUT => 5, /* \'initSQLCommands\' => [\'PRAGMA journal_mode=WAL\',], */';
|
||||
$this->c->DB_OPTIONS = [
|
||||
PDO::ATTR_TIMEOUT => 5,
|
||||
'initSQLCommands' => [
|
||||
'PRAGMA journal_mode=WAL',
|
||||
],
|
||||
];
|
||||
|
||||
break;
|
||||
case 'pgsql':
|
||||
if (\preg_match('%^([^:]+):(\d+)$%', $dbhost, $matches)) {
|
||||
|
@ -518,8 +532,6 @@ class Install extends Admin
|
|||
break;
|
||||
}
|
||||
|
||||
$this->c->DB_OPTIONS = [];
|
||||
|
||||
// подключение к БД
|
||||
try {
|
||||
$stat = $this->c->DB->statistics();
|
||||
|
@ -529,6 +541,28 @@ class Install extends Admin
|
|||
return $dbhost;
|
||||
}
|
||||
|
||||
$version = $versionNeed = $this->c->DB->getAttribute(PDO::ATTR_SERVER_VERSION);
|
||||
|
||||
switch ($dbtype) {
|
||||
case 'mysql_innodb':
|
||||
case 'mysql':
|
||||
$versionNeed = self::MYSQL_MIN;
|
||||
$progName = 'MySQL';
|
||||
|
||||
break;
|
||||
case 'sqlite':
|
||||
$versionNeed = self::SQLITE_MIN;
|
||||
$progName = 'SQLite';
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (\version_compare($version, $versionNeed, '<')) {
|
||||
$v->addError(['You are running error', $progName, $version, $this->c->FORK_REVISION, $versionNeed]);
|
||||
|
||||
return $dbhost;
|
||||
}
|
||||
|
||||
// проверка наличия таблицы пользователей в БД
|
||||
if ($this->c->DB->tableExists('users')) {
|
||||
$v->addError(['Existing table error', $v->dbprefix, $v->dbname]);
|
||||
|
@ -549,7 +583,7 @@ class Install extends Admin
|
|||
isset($stat['server_encoding'])
|
||||
&& 'UTF8' !== $stat['server_encoding']
|
||||
) {
|
||||
$v->addError('Bad database encoding');
|
||||
$v->addError(['Bad database encoding', 'UTF8']);
|
||||
}
|
||||
|
||||
// база PostgreSQL, порядок сопоставления/сортировки
|
||||
|
@ -568,6 +602,14 @@ class Install extends Admin
|
|||
$v->addError('Bad database ctype');
|
||||
}
|
||||
|
||||
// база SQLite, кодировка базы
|
||||
if (
|
||||
isset($stat['encoding'])
|
||||
&& 'UTF-8' !== $stat['encoding']
|
||||
) {
|
||||
$v->addError(['Bad database encoding', 'UTF-8']);
|
||||
}
|
||||
|
||||
return $dbhost;
|
||||
}
|
||||
|
||||
|
@ -840,16 +882,12 @@ class Install extends Admin
|
|||
'id' => ['SERIAL', false],
|
||||
'word' => ['VARCHAR(20)', false, '' , 'bin'],
|
||||
],
|
||||
'PRIMARY KEY' => ['word'],
|
||||
'INDEXES' => [
|
||||
'id_idx' => ['id'],
|
||||
'PRIMARY KEY' => ['id'],
|
||||
'UNIQUE KEYS' => [
|
||||
'word_idx' => ['word']
|
||||
],
|
||||
'ENGINE' => $this->DBEngine,
|
||||
];
|
||||
if ('sqlite' === $v->dbtype) { //????
|
||||
$schema['PRIMARY KEY'] = ['id'];
|
||||
$schema['UNIQUE KEYS'] = ['word_idx' => ['word']];
|
||||
}
|
||||
$this->c->DB->createTable('search_words', $schema);
|
||||
|
||||
// topic_subscriptions
|
||||
|
@ -1338,6 +1376,7 @@ class Install extends Admin
|
|||
$config = \str_replace($key, \addslashes($val), $config);
|
||||
}
|
||||
|
||||
$config = \str_replace('_DB_OPTIONS_', $this->c->DB_OPTS_AS_STR, $config);
|
||||
$result = \file_put_contents($this->c->DIR_APP . '/config/main.php', $config);
|
||||
|
||||
if (false === $result) {
|
||||
|
|
|
@ -31,8 +31,8 @@ class PreviousPost extends Action
|
|||
ORDER BY p.id DESC
|
||||
LIMIT 1";
|
||||
|
||||
$id = $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
$id = (int) $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
|
||||
return empty($id) ? null : $id;
|
||||
return $id ?: null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ class CalcStat extends Method
|
|||
FROM ::posts AS p
|
||||
WHERE p.topic_id=?i:tid';
|
||||
|
||||
$numReplies = $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
$numReplies = (int) $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
|
||||
$query = 'SELECT p.id, p.poster, p.poster_id, p.posted
|
||||
FROM ::posts AS p
|
||||
|
|
|
@ -256,9 +256,7 @@ class Topic extends DataModel
|
|||
FROM ::posts AS p
|
||||
WHERE p.topic_id=?i:tid AND p.posted>?i:visit';
|
||||
|
||||
$pid = $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
|
||||
return $pid ?: 0;
|
||||
return (int) $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -280,9 +278,7 @@ class Topic extends DataModel
|
|||
FROM ::posts AS p
|
||||
WHERE p.topic_id=?i:tid AND p.posted>?i:visit';
|
||||
|
||||
$pid = $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
|
||||
return $pid ?: 0;
|
||||
return (int) $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,7 +26,7 @@ class Stats extends Action
|
|||
FROM ::users AS u
|
||||
WHERE u.group_id!=?i:gid';
|
||||
|
||||
$total = $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
$total = (int) $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
|
||||
$query = 'SELECT u.id, u.username
|
||||
FROM ::users AS u
|
||||
|
|
|
@ -34,6 +34,6 @@ class UsersNumber extends Action
|
|||
FROM ::users AS u
|
||||
WHERE u.group_id=?i:gid';
|
||||
|
||||
return $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
return (int) $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
}
|
||||
}
|
||||
|
|
0
app/config/db/.gitkeep
Normal file
0
app/config/db/.gitkeep
Normal file
|
@ -19,7 +19,7 @@ return [
|
|||
'DB_DSN' => '_DB_DSN_',
|
||||
'DB_USERNAME' => '_DB_USERNAME_',
|
||||
'DB_PASSWORD' => '_DB_PASSWORD_',
|
||||
'DB_OPTIONS' => [],
|
||||
'DB_OPTIONS' => [_DB_OPTIONS_],
|
||||
'DB_PREFIX' => '_DB_PREFIX_',
|
||||
'COOKIE' => [
|
||||
'prefix' => '_COOKIE_PREFIX_',
|
||||
|
|
|
@ -207,20 +207,11 @@ msgstr "Test forum"
|
|||
msgid "This is just a test forum"
|
||||
msgstr "This is just a test forum"
|
||||
|
||||
msgid "Alert cache"
|
||||
msgstr "<b>The cache directory is currently not writable!</b> In order for ForkBB to function properly, the directory <em>%s</em> must be writable by PHP. Use chmod to set the appropriate directory permissions. If in doubt, chmod to 0777."
|
||||
|
||||
msgid "Alert avatar"
|
||||
msgstr "<b>The avatar directory is currently not writable!</b> If you want users to be able to upload their own avatar images you must see to it that the directory <em>%s</em> is writable by PHP. You can later choose to save avatar images in a different directory (see Admin/Options). Use chmod to set the appropriate directory permissions. If in doubt, chmod to 0777."
|
||||
|
||||
msgid "Alert upload"
|
||||
msgstr "<b>File uploads appear to be disallowed on this server!</b> If you want users to be able to upload their own avatar images you must enable the file_uploads configuration setting in PHP. Once file uploads have been enabled, avatar uploads can be enabled in Administration/Options/Features."
|
||||
|
||||
msgid "Bad database charset"
|
||||
msgstr "The database must be created with the character encoding <b>utf8mb4</b> (compare <b>utf8mb4_unicode_ci</b>)."
|
||||
|
||||
msgid "Bad database encoding"
|
||||
msgstr "The database must be created with the character encoding <b>UTF8</b>."
|
||||
msgstr "The database must be created with the character encoding <b>%s</b>."
|
||||
|
||||
msgid "Bad database collate"
|
||||
msgstr "The database must be created with Collation order <b>C</b> (LC_COLLATE)."
|
||||
|
|
|
@ -207,20 +207,11 @@ msgstr "Тестовый раздел"
|
|||
msgid "This is just a test forum"
|
||||
msgstr "Этот раздел создан при установке форума"
|
||||
|
||||
msgid "Alert cache"
|
||||
msgstr "<b>Папка кэша заблокирована для записи!</b> Для правильного функционирования ForkBB директория <em>%s</em> должна быть открыта для записи из PHP. Используйте chmod для установки прав на директорию. Если сомневаетесь, то установите права 0777."
|
||||
|
||||
msgid "Alert avatar"
|
||||
msgstr "<b>Папка для аватар заблокирована для записи!</b> Если вы хотите, чтобы пользователи форума использовали аватары, вы должны разрешить запись в директорию <em>%s</em> для PHP. Позже вы можете сменить директорию хранения аватар (смотрите Админка/Опции). Используйте chmod для установки прав на директорию. Если сомневаетесь, то установите права 0777."
|
||||
|
||||
msgid "Alert upload"
|
||||
msgstr "<b>Загрузка файлов, кажется, выключена на этом сервере!</b> Если вы хотите, чтобы пользователи форума использовали аватары, вы должны разрешить file_uploads в настройках вашего PHP. После разрешения загрузки файлов на сервер, вы можете разрешить использования аватар для пользователей форума (смотрите Админка/Опции)."
|
||||
|
||||
msgid "Bad database charset"
|
||||
msgstr "База данных должна быть создана с указанием кодировки символов <b>utf8mb4</b> (сравнение <b>utf8mb4_unicode_ci</b>)."
|
||||
|
||||
msgid "Bad database encoding"
|
||||
msgstr "База данных должна быть создана с указанием кодировки символов <b>UTF8</b>."
|
||||
msgstr "База данных должна быть создана с указанием кодировки символов <b>%s</b>."
|
||||
|
||||
msgid "Bad database collate"
|
||||
msgstr "База данных должна быть создана с порядком сопоставления <b>C</b> (LC_COLLATE)."
|
||||
|
|
|
@ -15,7 +15,7 @@ No: plugins/extensions system, ...
|
|||
* PHP 7.3+
|
||||
* PHP extensions: pdo, intl, json, mbstring, fileinfo
|
||||
* PHP extensions (desirable): gd or imagick (for upload avatars and other images)
|
||||
* A database such as MySQL 5.5.3+, PostgreSQL 10+(?) (_Drivers for other databases are not realized now_)
|
||||
* A database such as MySQL 5.5.3+, SQLite 3.25+, PostgreSQL 10+(?)
|
||||
|
||||
## Install
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue