супер попа (обновление)

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2022-04-16 17:30:06 +10:00
parent c8025acc79
commit 30fd8f0ec3
14 changed files with 623 additions and 338 deletions

View File

@ -36,12 +36,12 @@ final class accounts_controller extends core
public function registration(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['account' => []];
$vars['errors'] = [];
if ($vars['account'] = accounts::registration(email: $vars['email'] ?? null, password: $vars['password'] ?? null, errors: $vars['errors']['account'])) {
if ($vars['account'] = accounts::registration(mail: $vars['mail'] ?? null, password: $vars['password'] ?? null, errors: $vars['errors'])) {
// Удалось зарегистрироваться
if ($vars['account'] = accounts::authentication($vars['email'] ?? null, $vars['password'] ?? null, (bool) ($vars['remember'] ?? false), $vars)) {
if ($vars['account'] = accounts::authentication($vars['mail'] ?? null, $vars['password'] ?? null, (bool) ($vars['remember'] ?? false), $vars)) {
// Удалось аутентифицироваться
} else {
// Не удалось аутентифицироваться
@ -75,9 +75,9 @@ final class accounts_controller extends core
public function authentication(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['account' => []];
$vars['errors'] = [];
if ($vars['account'] = accounts::authentication($vars['email'] ?? null, $vars['password'] ?? null, (bool) ($vars['remember'] ?? false), errors: $vars['errors']['account'])) {
if ($vars['account'] = accounts::authentication($vars['mail'] ?? null, $vars['password'] ?? null, (bool) ($vars['remember'] ?? false), errors: $vars['errors'])) {
// Удалось аутентифицироваться
} else {
// Не удалось аутентифицироваться
@ -105,9 +105,9 @@ final class accounts_controller extends core
public function deauthentication(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['account' => []];
$vars['errors'] = [];
if (accounts::deauthentication(errors: $vars['errors']['account'])) {
if (accounts::deauthentication(errors: $vars['errors'])) {
// Удалось деаутентифицироваться
// Деинициализация аккаунта
@ -137,21 +137,21 @@ final class accounts_controller extends core
public function data(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['account' => []];
$vars['errors'] = [];
if ($account = accounts::read(['id' => $vars['id']], $vars['errors'])) {
// Найдены данные запрашиваемого аккаунта
// Инициализация аккаунта
$vars['account'] = accounts::account($vars['errors']);
$vars['account'] = accounts::init(errors: $vars['errors']);
if ($vars['account'] && $vars['account']['permissions']['accounts'] ?? 0 === 1) {
if ($vars['account'] && $vars['account']->permissions['accounts'] ?? 0 === 1) {
// Удалось аутентифицироваться и пройдена проверка авторизации
} else {
// Не удалось аутентифицироваться
// Удаление запрещённых к публикации полей
$account['password'] = $account['hash'] = $account['time'] = null;
$account->password = $account->hash = $account->time = null;
}
// Генерация ответа

View File

@ -18,29 +18,30 @@ use mirzaev\surikovlib\models\books_model as books;
final class books_controller extends core
{
/**
* Страница с библеотекой
* Страница с книгами (или книгой)
*
* @param array $vars
*/
public function index(array $vars = []): ?string
{
// Проверка аутентифицированности
$vars['account'] = accounts::account($vars);
// Инициализация журнала ошибок
$vars['errors'] = [];
if (!empty($vars['id'])) {
// Проверка аутентифицированности
$vars['account'] = accounts::init(errors: $vars['errors']);
if (isset($vars['id'])) {
// Определённая книга
// Чтение книги
// Чтение метаданных книги
$vars['book'] = books::read(['id' => $vars['id']])[0] ?? null;
if (empty($vars['book'])) {
// Не найдена книга
return null;
}
// Инициализация страницы
if (empty($vars['page']) || $vars['page'] < 0) $vars['page'] = 0;
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . 'book.html', $vars);
} else {
// Все книги
@ -52,40 +53,6 @@ final class books_controller extends core
}
}
/**
* Обложка
*
* @param array $vars
*
* @return string|null Файл, если найден
*/
public function cover(array $vars = []): ?string
{
// Проверка входных параметров
if (empty($vars['id'])) return null;
// Инициализация пути до файла
$file = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $vars['id'] . DIRECTORY_SEPARATOR . '0.jpg';
if (file_exists($file)) {
// Найден файл
// Настройка заголовков
header('Content-Description: File Transfer');
header('Content-Type: image/jpeg');
header('Content-Disposition: attachment; filename=' . basename($file));
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($file));
// Очистить буфер вывода
ob_end_clean();
return file_get_contents($file);
}
return null;
}
/**
* Запись
*
@ -96,59 +63,54 @@ final class books_controller extends core
*/
public function write(array $vars = [], array $files = []): ?string
{
// ПЕРЕНЕСТИ В МОДЕЛЬ
// Инициализация журнала ошибок
$vars['errors'] = [];
// Инициализация буфера сохранённых книг
$books = [];
for ($i = -1; count($files['books']['name']) > ++$i;) {
// Перебор загруженных книг
// Генерация хеша файла
$hash = hash_file('md5', $files['books']['tmp_name'][$i]) ?? 0;
if (move_uploaded_file($files['books']['tmp_name'][$i], \STORAGE . DIRECTORY_SEPARATOR . 'temp' . DIRECTORY_SEPARATOR . $hash . '_' . $files['books']['name'][$i])) {
// Загружен и перемещён из временной папки файл с книгой
// Запись в буфер сохранённых книг
$books[] = $hash . '_' . $files['books']['name'][$i];
}
}
foreach ($books as $book) {
// Перебор сохранённых книг
// Запись в базу данных
// Инициализация пути до хранилища
$storage = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . 'id' . DIRECTORY_SEPARATOR;
// Извлечение изображений из PDF-документа
exec("cd $storage && pdfimages -j $book");
if (count($books = books::import($files['books'] ?? [], errors: $vars['errors'])) > 0) {
// Загружены книги
} else {
// Не загружены книги
}
// Перенаправление
header('Location: /books', response_code: 303);
return null;
return 'wtf';
}
/**
* Чтение
*
* @param array $vars Параметры запроса
* @param array $vars
*
* @return string|null HTML-документ
* @return string|null Файл, если найден
*/
public function read(array $vars = []): ?string
{
if (accounts::deauthentication($vars)) {
// Удалось деаутентифицироваться
if (isset($vars['id'], $vars['file'])) {
// Найдены обязательные входные параметры
// Инициализация пути до файла
$file = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $vars['id'] . DIRECTORY_SEPARATOR . $vars['file'] . '.jpg';
if (file_exists($file)) {
// Найден файл
// Настройка заголовков
header('Content-Description: File Transfer');
header('Content-Type: image/jpeg');
header('Content-Disposition: attachment; filename=' . basename($file));
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($file));
// Очистить буфер вывода
ob_end_clean();
return file_get_contents($file);
}
}
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'main' . DIRECTORY_SEPARATOR . 'index.html', $vars);
return null;
}
/**
@ -160,11 +122,5 @@ final class books_controller extends core
*/
public function delete(array $vars = []): ?string
{
if (accounts::deauthentication($vars)) {
// Удалось деаутентифицироваться
}
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'main' . DIRECTORY_SEPARATOR . 'index.html', $vars);
}
}

View File

@ -20,8 +20,11 @@ final class main_controller extends core
{
public function index(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = [];
// Проверка аутентифицированности
$vars['account'] = accounts::account($vars);
$vars['account'] = accounts::init(errors: $vars['errors']);
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'main' . DIRECTORY_SEPARATOR . 'index.html', $vars);

View File

@ -15,33 +15,86 @@ use exception;
*/
final class accounts_model extends core
{
/**
* Идентификатор
*/
public int $id;
/**
* Почта
*/
public string $mail;
/**
* Пароль
*/
public string $password;
/**
* Хеш
*/
public string $hash;
/**
* Время активности хеша
*/
public int $time;
/**
* Время активности хеша
*/
public array $permissions;
/**
* Конструктор
*
* @param array $vars Параметры
*/
public function __construct(array $vars = []) {
foreach ($vars as $key => $value) {
// Перебор параметров
// Запись свойства
if (property_exists($this, $key)) $this->$key = $value;
}
}
/**
* Регистрация
*
* @param string $name Входной псевдоним
* @param string $email Почта
* @param string $password Пароль (password)
* @param bool $authentication Автоматическая аутентификация в случае успешной регистрации
* @param string $mail Почта
* @param string $password Пароль
* @param bool $authenticate Автоматическая аутентификация в случае успешной регистрации
* @param array &$errors Журнал ошибок
*
* @return array|bool Аккаунт, если удалось аутентифицироваться
* @return static|null Аккаунт
*/
public static function registration(string $name = null, string $email = null, string $password, array &$errors = []): array
public static function registration(string $mail, string $password, bool $authenticate = true, array &$errors = []): ?static
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
if (static::account($errors)) {
if (static::init(errors: $errors)) {
// Аутентифицирован пользователь
// Запись ошибки
throw new exception('Уже аутентифицирован');
}
if (empty($account = static::read(['name' => $name]) or $account = static::read(['email' => $email]))) {
if (empty($account = static::read(['mail' => $mail]))) {
// Не удалось найти аккаунт
if (static::write($name, $email, $password, $errors)) {
if (static::write($mail, $password, $errors)) {
// Удалось зарегистрироваться
if ($authenticate) {
// Запрошена аутентификация
// Аутентификация
$account = static::authentication($mail, $password, true, $errors);
}
return $account;
}
} else {
@ -51,7 +104,7 @@ final class accounts_model extends core
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@ -59,41 +112,44 @@ final class accounts_model extends core
];
}
return [];
return null;
}
/**
* Аутентификация
*
* @param string $login Входной псевдоним
* @param string $password Пароль (password)
* @param string $mail Почта
* @param string $password Пароль
* @param bool $remember Функция "Запомнить меня" - увеличенное время хранения cookies
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт (если не найден, то пустой массив)
* @return static|null Аккаунт
*/
public static function authentication(string $login, string $password, bool $remember = false, array &$errors = []): array
public static function authentication(string $mail, string $password, bool $remember = false, array &$errors = []): ?static
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
if (static::account($errors)) {
if (static::init(errors: $errors)) {
// Аутентифицирован пользователь
// Запись ошибки
throw new exception('Уже аутентифицирован');
}
if (empty($account = static::read(['name' => $login]) or $account = static::read(['email' => $login]))) {
if (empty($account = static::read(['mail' => $mail]))) {
// Не удалось найти аккаунт
throw new exception('Не удалось найти аккаунт');
}
if (password_verify($password, $account['password'])) {
if (password_verify($password, $account->password)) {
// Совпадают хеши паролей
// Инициализация идентификатора сессии
session_id($account['id']);
session_id((string) $account->id);
// Инициализация названия сессии
session_name('id');
@ -105,10 +161,10 @@ final class accounts_model extends core
$time = time() + ($remember ? 604800 : 86400);
// Инициализация хеша
$hash = static::hash((int) $account['id'], crypt($account['password'], time() . $account['id']), $time, $errors)['hash'];
$hash = static::hash((int) $account->id, crypt($account->password, time() . $account->id), $time, $errors)['hash'];
// Инициализация cookies
setcookie("hash", $hash, $time, path: '/', secure: true);
setcookie('hash', $hash, $time, path: '/', secure: true);
return $account;
} else {
@ -118,7 +174,7 @@ final class accounts_model extends core
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@ -126,7 +182,7 @@ final class accounts_model extends core
];
}
return [];
return null;
}
/**
@ -138,8 +194,11 @@ final class accounts_model extends core
*/
public static function deauthentication(array &$errors = []): bool
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
if ($account = static::account($errors)) {
if ($account = static::init(errors: $errors)) {
// Аутентифицирован пользователь
// Инициализация запроса
@ -147,7 +206,7 @@ final class accounts_model extends core
// Параметры запроса
$params = [
":id" => $account['id'],
":id" => $account->id,
];
// Отправка запроса
@ -157,8 +216,8 @@ final class accounts_model extends core
$request->fetch(pdo::FETCH_ASSOC);
// Деинициализация cookies
setcookie("id", '', 0, path: '/', secure: true);
setcookie("hash", '', 0, path: '/', secure: true);
setcookie('id', '', 0, path: '/', secure: true);
setcookie('hash', '', 0, path: '/', secure: true);
return true;
} else {
@ -169,7 +228,7 @@ final class accounts_model extends core
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@ -181,21 +240,30 @@ final class accounts_model extends core
}
/**
* Прочитать данные аккаунта, если пользователь аутентифицирован
*
* Можно использовать как проверку на аутентифицированность
* Инициализация
*
* @param int|null $account Аккаунт (идентификатор)
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт (если не найден, то пустой массив)
*
* @todo 1. Сделать в static::read() возможность передачи нескольких параметров и перенести туда непосредственно чтение аккаунта с проверкой хеша
* @return static|null Аккаунт
*/
public static function account(array &$errors = []): array
public static function init(?int $account = null, array &$errors = []): ?static
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
if (!empty($_COOKIE['id']) && !empty($_COOKIE['hash'])) {
// Аутентифицирован аккаунт (найдены cookie и они хранят значения - подразумевается, что не null или пустое)
if (isset($account)) {
// Получен идентификатор аккаунта
if (empty($account = static::read(['id' => $account]))) {
// Не найден аккаунт
// Генерация ошибки
throw new exception('Не найден пользователь');
}
} else if (!empty($_COOKIE['id']) && !empty($_COOKIE['hash'])) {
// Найдены cookie с данными аккаунта (подразумевается, что он аутентифицирован)
if ($_COOKIE['hash'] === static::hash((int) $_COOKIE['id'], errors: $errors)['hash']) {
// Совпадает переданный хеш с тем, что хранится в базе данных
@ -206,34 +274,28 @@ final class accounts_model extends core
throw new exception('Вы аутентифицированы с другого устройства (не совпадают хеши аутентификации)');
}
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `accounts` WHERE `id` = :id && `hash` = :hash");
// Параметры запроса
$params = [
":id" => $_COOKIE['id'],
":hash" => $_COOKIE['hash'],
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
if (empty($account = $request->fetch(pdo::FETCH_ASSOC))) {
// Не найдена связка идентификатора с хешем
if (empty($account = static::read([
'id' => $_COOKIE['id'],
'hash' => $_COOKIE['hash']
]))) {
// Не найден аккаунт или связка аккаунта с хешем
// Генерация ошибки
throw new exception('Не найден пользотватель или время аутентификации истекло');
throw new exception('Не найден пользователь или время аутентификации истекло');
}
} else {
// Не найдены параметры для поиска аккаунта
// Чтение разрешений
$account['permissions'] = static::permissions((int) $account['id'], $errors);
return $account;
return null;
}
// Чтение разрешений
$account->permissions = static::permissions((int) $account->id, $errors);
return $account;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
$errors['account'][]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@ -241,11 +303,12 @@ final class accounts_model extends core
];
}
return [];
return null;
}
/**
* Прочитать разрешения аккаунта
* Прочитать разрешения из базы данных
*
* @param int $id Идентификатор аккаунта
* @param array &$errors Журнал ошибок
@ -254,6 +317,9 @@ final class accounts_model extends core
*/
public static function permissions(int $id, array &$errors = []): array
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `permissions` WHERE `id` = :id");
@ -280,7 +346,7 @@ final class accounts_model extends core
return $response;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@ -302,14 +368,17 @@ final class accounts_model extends core
*/
public static function access(string $permission, int|null $id = null, array &$errors = []): ?bool
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
// Инициализация аккаунта
$account = isset($id) ? self::read(['id' => $id], $errors) : self::account($errors);
return isset($account['permissions'][$permission]) ? (bool) $account['permissions'][$permission] : null;
return isset($account->permissions[$permission]) ? (bool) $account->permissions[$permission] : null;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
$errors['account'][]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@ -321,114 +390,57 @@ final class accounts_model extends core
}
/**
* Запись пользователя в базу данных
* Запись в базу данных
*
* @param string|null $name Имя
* @param string|null $email Почта
* @param string|null $password Пароль
* @param string $mail Почта
* @param string $password Пароль
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт (если не найден, то пустой массив)
* @return static|null Аккаунт
*/
public static function write(string|null $name = null, string|null $email = null, string|null $password = null, array &$errors = []): array
public static function write(string $mail, string $password, array &$errors = []): ?static
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
// Инициализация параметров запроса
$params = [];
if (isset($name)) {
try {
// Проверка параметра
if (iconv_strlen($name) < 3) throw new exception('Длина имени должна быть не менее 3 символов');
if (iconv_strlen($name) > 60) throw new exception('Длина имени должна быть не более 60 символов');
// Запись в буфер параметров запроса
$params[':name'] = $name;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
];
goto end;
}
}
if (isset($email)) {
try {
// Проверка параметра
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) throw new exception('Не удалось распознать почту');
if (iconv_strlen($email) < 3) throw new exception('Длина почты должна быть не менее 3 символов');
if (iconv_strlen($email) > 60) throw new exception('Длина почты должна быть не более 80 символов');
// Запись в буфер параметров запроса
$params[':email'] = $email;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
];
goto end;
}
}
if (isset($password)) {
try {
// Проверка параметра
if (iconv_strlen($password) < 3) throw new exception('Длина пароля должна быть не менее 3 символов');
if (iconv_strlen($password) > 60) throw new exception('Длина пароля должна быть не более 120 символов');
// Запись в буфер параметров запроса
$params[':password'] = password_hash($password, PASSWORD_BCRYPT);
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
];
goto end;
}
}
// Инициализация запроса
$request = static::$db->prepare("INSERT INTO `accounts` (" . (isset($name) ? '`name`' : '') . (isset($name) && isset($email) ? ', ' : '') . (isset($email) ? '`email`' : '') . ((isset($name) || isset($email)) && isset($password) ? ', ' : '') . (isset($password) ? '`password`' : '') . ") VALUES (" . (isset($name) ? ':name' : '') . (isset($name) && isset($email) ? ', ' : '') . (isset($email) ? ':email' : '') . ((isset($name) || isset($email)) && isset($password) ? ', ' : '') . (isset($password) ? ':password' : '') . ")");
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
try {
if (isset($name)) {
// Передано имя аккаунта
// Проверка параметра
if (filter_var($mail, FILTER_VALIDATE_mail) === false) throw new exception('Не удалось распознать почту');
if (iconv_strlen($mail) < 3) throw new exception('Длина почты должна быть не менее 3 символов');
if (iconv_strlen($mail) > 60) throw new exception('Длина почты должна быть не более 80 символов');
// Чтение аккаунта
$account = static::read(['name' => $name]);
} else if (isset($email)) {
// Передана почта аккаунта
// Запись в буфер параметров запроса
$params[':mail'] = $mail;
// Чтение аккаунта
$account = static::read(['email' => $email]);
} else {
// Не передано ни имя, ни почта
// Проверка параметра
if (iconv_strlen($password) < 3) throw new exception('Длина пароля должна быть не менее 3 символов');
if (iconv_strlen($password) > 60) throw new exception('Длина пароля должна быть не более 120 символов');
throw new exception('Не переданны данные для полноценной регистрации аккаунта');
}
// Запись в буфер параметров запроса
$params[':password'] = password_hash($password, PASSWORD_BCRYPT);
// Инициализация запроса
$request = static::$db->prepare("INSERT INTO `accounts` (" . (isset($name) ? '`name`' : '') . (isset($name) && isset($mail) ? ', ' : '') . (isset($mail) ? '`mail`' : '') . ((isset($name) || isset($mail)) && isset($password) ? ', ' : '') . (isset($password) ? '`password`' : '') . ") VALUES (" . (isset($name) ? ':name' : '') . (isset($name) && isset($mail) ? ', ' : '') . (isset($mail) ? ':mail' : '') . ((isset($name) || isset($mail)) && isset($password) ? ', ' : '') . (isset($password) ? ':password' : '') . ")");
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
// Чтение аккаунта
$account = static::read(['mail' => $mail]);
// Инициализация запроса
$request = static::$db->prepare("INSERT INTO `permissions` (`id`) VALUES (:id)");
// Инициализация параметров
$params = [
':id' => $account['id']
':id' => $account->id
];
// Отправка запроса
@ -438,7 +450,7 @@ final class accounts_model extends core
$request->fetch(pdo::FETCH_ASSOC);
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@ -447,7 +459,7 @@ final class accounts_model extends core
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@ -458,52 +470,58 @@ final class accounts_model extends core
// Конец выполнения
end:
return isset($account) && $account ? $account : [];
return $account ?? [];
}
/**
* Чтение пользователя из базы данных
* Чтение из базы данных
*
* @param array $search Поиск ('поле' => 'значение'), работает только с одним полем
* @param array $expression Выражение поиска ('поле' => 'значение')
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт, если найден
* @return static|null Аккаунт
*/
public static function read(array $search, array &$errors = []): array
public static function read(array $expression, array &$errors = []): ?static
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
// Инициализация данных для поиска
$field = array_keys($search)[0] ?? null;
$value = $search[$field] ?? null;
// Инициализация выражения поиска
$where = 'WHERE ';
if (empty($field)) {
// Получено пустое значение поля
// Инициализация параметров запроса
$params = [];
// Запись ошибки
throw new exception('Пустое значение поля для поиска');
foreach ($expression as $parameter => $value) {
// Перебор выражения поиска
// Запись в строку запроса
$where .= "`$parameter` = :$parameter &&";
// Запись параметров запроса
$params[":$parameter"] = $value;
}
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `accounts` WHERE `$field` = :field LIMIT 1");
// Очистка или реинициализация выражения поиска
$where = empty($expression) ? '' : trim(trim($where, '&&'));
// Параметры запроса
$params = [
":field" => $value,
];
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `accounts` $where LIMIT 1");
// Отправка запроса
$request->execute($params);
// Генерация ответа
if ($account = $request->fetch(pdo::FETCH_ASSOC)) {
if ($account = new static($request->fetch(pdo::FETCH_ASSOC))) {
// Найден аккаунт
try {
if ($permissions = static::permissions((int) $account['id'], $errors)) {
if ($permissions = static::permissions((int) $account->id, $errors)) {
// Найдены разрешения
// Запись в буфер данных аккаунта
$account['permissions'] = $permissions;
$account->permissions = $permissions;
} else {
// Не найдены разрешения
@ -511,7 +529,7 @@ final class accounts_model extends core
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
@ -524,7 +542,7 @@ final class accounts_model extends core
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
$errors['account'][]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@ -532,7 +550,7 @@ final class accounts_model extends core
];
}
return isset($account) && $account ? $account : [];
return $account ?? null;
}
/**
@ -547,6 +565,9 @@ final class accounts_model extends core
*/
public static function hash(int $id, string|null $hash = null, int|null $time = null, array &$errors = []): array
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
if (isset($hash, $time)) {
// Переданы хеш и его время хранения
@ -608,7 +629,7 @@ final class accounts_model extends core
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),

View File

@ -4,6 +4,8 @@ declare(strict_types=1);
namespace mirzaev\surikovlib\models;
use mirzaev\surikovlib\models\accounts_model as accounts;
use pdo;
use exception;
@ -15,35 +17,260 @@ use exception;
*/
final class books_model extends core
{
public static function read(array $where = [], int $limit = 1, int $page = 1): array
/**
* Чтение
*
* @param array $expression Выражение поиска
* @param int $limit Ограничение по количеству
* @param int $page Страница (сдвиг)
* @param array &$errors Журнал ошибок
*
* @return array Книги
*/
public static function read(array $expression = [], int $limit = 1, int $page = 1, array &$errors = []): array
{
// Инициализация строки поиска
$row = '';
// Инициализация журнала ошибок
$errors['books'] ?? $errors['books'] = [];
// Инициализация параметров запроса
$params = [];
try {
// Инициализация выражения поиска
$where = 'WHERE ';
foreach ($where as $parameter => $value) {
// Перебор параметров поиска
// Инициализация параметров запроса
$params = [];
// Запись функции
if(empty($row)) $row = 'WHERE ';
foreach ($expression as $parameter => $value) {
// Перебор выражения поиска
// Запись в строку поиска
$row .= "`$parameter` = :$parameter";
// Запись в строку запроса
$where .= "`$parameter` = :$parameter &&";
$params[$parameter] = $value;
// Запись параметров запроса
$params[":$parameter"] = $value;
}
// Очистка или реинициализация выражения поиска
$where = empty($expression) ? '' : trim(trim($where, '&&'));
// Инициализация страницы
$page = $limit * --$page;
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `books` $where LIMIT $page, $limit");
// Отправка запроса
$request->execute($params);
return (array) $request->fetchAll(pdo::FETCH_ASSOC);
} catch (exception $e) {
// Запись в журнал ошибок
$errors['books'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
// Инициализация страницы
$page = $limit * --$page;
return [];
}
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `books` $row LIMIT $page, $limit");
/**
* Запись в базу данных
*
* @param string $title Название
* @param string|null $description Описание
* @param int|null $account Аккаунт (идентификатор)
* @param array &$errors Журнал ошибок
*
* @return int|null Идентификатор записанной книги
*/
public static function write(string $title, ?string $description = null, ?int $account = null, array &$errors = []): ?int
{
// Инициализация журнала ошибок
$errors['books'] ?? $errors['books'] = [];
// Отправка запроса
$request->execute($params);
try {
// Инициализация аккаунта
$account = accounts::init($account, $errors);
return (array) $request->fetchAll(pdo::FETCH_ASSOC);
if (empty($account) || !accounts::access('books', $account->id)) {
// Не удалось найти аккаунт или разрешение на управление книгами не выдано
throw new exception('У вас нет разрешения на управление книгами');
}
// Инициализация запроса
$request = static::$db->prepare("INSERT INTO `books` (`account`, `title`, `description`) VALUES (:account, :title, :description)");
// Инициализация параметров
$params = [
':account' => $account->id,
':title' => $title,
':description' => $description
];
// Отправка запроса
$request->execute($params);
if ($id = static::$db->lastInsertId()) {
// Получен идентификатор загруженной книги (подразумевается)
return (int) $id;
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors['books'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Импорт
*
* @param array $books Книги (файлы)
* @param int|null $account Аккаунт (идентификатор)
* @param array &$errors Журнал ошибок
*
* @return array Записанные книги
*/
public static function import(array $books, ?int $account = null, array &$errors = []): array
{
// Инициализация журнала ошибок
$errors['books'] ?? $errors['books'] = [];
try {
if (empty($books)) {
// Не найдены книги
throw new exception('Не найдены книги для записи');
}
// Инициализация аккаунта
$account = accounts::init($account, $errors);
if (empty($account) || !accounts::access('books', $account->id)) {
// Не найден аккаунт или разрешение на управление книгами не выдано
throw new exception('У вас нет разрешения на управление книгами');
}
// Инициализация буфера инициализированных книг
$initialized = [];
for ($i = -1; count($books['name']) > ++$i;) {
// Перебор загруженных книг
// Генерация хеша файла
$hash = hash_file('md5', $books['tmp_name'][$i]) ?? 0;
if (move_uploaded_file($books['tmp_name'][$i], \STORAGE . DIRECTORY_SEPARATOR . 'temp' . DIRECTORY_SEPARATOR . $hash . '_' . $books['name'][$i])) {
// Загружен и перемещён из временной папки файл с книгой
// Извлечение имени файла
// Запись в буфер инициализированных книг
$initialized[] = [
'name' => preg_replace('/\.pdf/', '', $books['name'])[0],
'file' => $hash . '_' . $books['name'][$i]
];
}
}
// Инициализация буфера записанных книг
$writed = [];
foreach ($initialized as $book) {
// Перебор инициализированных книг
try {
if ($id = static::write($book['name'], 'Без описания', $account->id ?? null, $errors)) {
// Записана в базу данных книга
// Инициализация пути до хранилища
$directory = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $id . DIRECTORY_SEPARATOR;
// Инициализация директории
if (!file_exists($directory)) if (!mkdir($directory, 0755, true)) throw new exception('Не удалось записать директорию для книги');
// Инициализация пути до временного файла
$file = \STORAGE . DIRECTORY_SEPARATOR . 'temp' . DIRECTORY_SEPARATOR . $book['file'];
// Извлечение изображений из PDF-документа
exec("pdfimages -j '$file' '$directory'");
// Переименование файлов в необходимый формат
exec("echo 'export j=-1; for i in $directory*.jpg; do let j+=1; mv \$i $directory\$j.jpg; done' | bash");
// Запись в буфер записанных книг
$writed[] = $id;
} else {
// Не записана в базу данных книга
throw new exception('Не удалось записать книгу в базу данных', 500);
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors['books'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors['books'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return $writed ?? [];
}
/**
* Подсчёт количества страниц
*
* @param int $id Идентификатор
* @param array &$errors Журнал ошибок
*
* @return int|null Количество страниц
*/
public static function amount(int $id, array &$errors = []): ?int
{
// Инициализация журнала ошибок
$errors['books'] ?? $errors['books'] = [];
try {
// Инициализация счётчика
$amount = -1;
while (true) {
// Перебор директорий (!!! Рекурсия !!!)
// Перебор изображений по возрастанию (от 0.jpg до 999.jpg и т.д.)
if (!file_exists(\STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $id . DIRECTORY_SEPARATOR . ++$amount . '.jpg')) return $amount;
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors['books'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
}

View File

@ -65,3 +65,13 @@
list-style: none;
background-color: #ae8f8f;
}
#authentication>div#account>p>b {
margin-right: 8px;
}
#authentication>div#account>p>span {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}

View File

@ -8,9 +8,8 @@ main>section#books>* {
}
main>section#books>form.upload {
width: calc(100% / 3 - 20px - 9px * 2);
height: calc(220px - 9px * 2);
margin: 5px;
width: calc(100% / 3 - 20px - 8px);
height: 212px;
margin-right: 20px;
}
@ -22,31 +21,86 @@ main>section#books>form.upload>p {
main>section#books>article.book {
width: calc(100% / 3 - 20px);
height: 220px;
position: relative;
margin-right: 20px;
display: flex;
flex-direction: column;
}
main>section#books>article.book:nth-child(3) {
width: calc(100% / 3);
main>section#books>article.book:nth-child(3n) {
margin-right: unset;
}
main>section#books>:is(form.upload, article.book):nth-last-child(1),
main>section#books>:is(form.upload, article.book):nth-last-child(2),
main>section#books>:is(form.upload, article.book):nth-last-child(3) {
margin-bottom: 0;
}
main>section#books>article.book>img {
height: 220px;
height: 100%;
object-fit: cover;
object-position: right;
overflow: hidden;
clip-path: polygon(5px calc(100% - 5px), calc(100% - 5px) calc(100% - 5px), calc(100% - 5px) 5px, 5px 5px);
/* clip-path: polygon(5px calc(100% - 5px), calc(100% - 5px) calc(100% - 5px), calc(100% - 5px) 5px, 5px 5px); */
}
main>section#books>article.book>h4 {
margin-top: 5px;
margin-bottom: 10px;
height: 50px;
text-align: center;
width: calc(100% - 20px);
position: absolute;
bottom: 0;
margin: 0;
padding: 10px;
text-align: left;
overflow-wrap: break-word;
-webkit-hyphens: auto;
-moz-hyphens: auto;
-ms-hyphens: auto;
hyphens: auto;
color: #fff;
background: rgba(0, 0, 0, 50%);
}
main>section#books>article.book>p {
margin: unset;
}
main>section#book>img,
main>section#book>img::before {
margin-bottom: 20px;
width: 100%;
}
main>section#book>img::before{
margin-top: -1em;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
main>section#book>nav>ul {
margin: 0;
padding: 0;
display: flex;
list-style: none;
}
main>section#book>nav>ul>li[type="button"] {
padding: 6px 20px;
}
main>section#book>nav>ul>li:only-of-type {
margin-left: auto;
margin-right: auto;
}
main>section#book>nav>ul>li.previous {
margin-left: auto;
margin-right: 15px;
}
main>section#book>nav>ul>li.next {
margin-right: auto;
}

View File

@ -3,11 +3,16 @@
text-decoration: none;
}
::selection,
::-moz-selection {
background-color: #544f7f;
}
body {
margin: 0;
display: grid;
grid-template-rows: auto auto 150px;
grid-template-columns: auto 300px 600px auto;
grid-template-columns: minmax(100px, auto) 300px minmax(500px, auto) minmax(100px, auto);;
background-color: #e5ddd1;
}
@ -54,8 +59,9 @@ main {
}
main>section>h2 {
margin-left: 1.5rem;
margin-top: unset;
margin-top: 0;
margin-bottom: 2rem;
text-align: center;
font-size: 1.8rem;
}
@ -168,7 +174,7 @@ footer {
}
.button,
:is(a, label)[type="button"],
:is(li, a, label)[type="button"],
input[type="submit"] {
padding: 10px 20px;
cursor: pointer;
@ -180,14 +186,14 @@ input[type="submit"] {
}
.button:hover,
:is(a, label)[type="button"]:hover,
:is(li, a, label)[type="button"]:hover,
input[type="submit"]:hover {
color: #fff;
background-color: #c5531f;
}
.button:active:is(:active, :focus),
:is(a, label)[type="button"]:is(:active, :focus),
:is(li, a, label)[type="button"]:is(:active, :focus),
input[type="radio"]:checked+label[type="button"],
input[type="submit"]:is(:active, :focus) {
color: #ddd;
@ -247,6 +253,11 @@ input:is([type="text"], [type="password"]).measured+.unit {
user-select: none;
}
.unselectable::selection,
.unselectable *::-moz-selection {
background: none;
}
.divider {
width: 100%;
border-radius: 2px;

View File

@ -2,15 +2,15 @@
<link href="/css/auth.css" rel="stylesheet">
{% if account is not empty %}
<h3>Аккаунт</h3>
<div>
<p><b>Почта:</b> <span>{{ account.email }}</span></p>
<a class="exit" type="button" href='/account/deauthentication'>Выход</a>
<h3 class="unselectable">Аккаунт</h3>
<div id="account">
<p><b class="unselectable">Почта:</b><span title="{{ account.mail }}">{{ account.mail }}</span></p>
<a class="exit unselectable" type="button" href='/account/deauthentication'>Выход</a>
</div>
{% else %}
<h3>Аутентификация</h3>
<h3 class="unselectable">Аутентификация</h3>
<form method="POST" accept-charset="UTF-8">
<input type="text" name="email" placeholder="Почта">
<input type="text" name="mail" placeholder="Почта">
<input type="password" name="password" placeholder="Пароль">
<div class="submit">

View File

@ -3,9 +3,16 @@
{% block main %}
<link href="/css/books.css" rel="stylesheet">
<article class="book">
<img src="/books/{{ book.id|e }}/cover" alt='Обложка книги "{{ book.title|e }}"'>
<h3><a href="/books/{{ book.id|e }}">{{ book.title|e }}</a></h3>
<p>{{ book.description|e|length|trim(' ') > 80 ? book.description|e|slice(0, 80)|trim(' ') ~ '...' : book.description|e}}</p>
</article>
<section id="book">
<h2>{{ book.title|e }}</h2>
<img class="unselectable" src="/storage/books/{{ book.id|e }}/{{ page|e }}" alt='Страница отсутствует'>
<nav>
<ul>
{% if page != 0 %}
<li class="previous unselectable" type="button"><a href="/books/{{ book.id|e }}/{{ page|e - 1 }}" title="Страница №{{ page|e - 1 }}">Назад</a></li>
{% endif %}
<li class="next unselectable" type="button"><a href="/books/{{ book.id|e }}/{{ page|e + 1 }}" title="Страница №{{ page|e + 1 }}">Вперёд</a></li>
</ul>
</nav>
</section>
{% endblock %}

View File

@ -5,13 +5,17 @@
<link href="/css/upload.css" rel="stylesheet">
<section id="books">
<form class="upload" action="/books/write" enctype="multipart/form-data" method="POST">
{% if account is not empty %}
{% if account.permissions.books is defined and account.permissions.books == 1 %}
<form class="upload unselectable" action="/storage/books/write" enctype="multipart/form-data" method="POST">
<input type="file" name="books[]" accept=".pdf" oninput="this.parentElement.submit();" multiple="true">
<p>+</p>
</form>
{% endif %}
{% endif %}
{% for book in books %}
<article class="book">
<img src="/books/{{ book.id|e }}/cover" alt='Обложка книги "{{ book.title|e }}"'>
<img src="/storage/books/{{ book.id|e }}/0" class="unselectable" alt='Обложка книги "{{ book.title|e }}"'>
<h4><a href="/books/{{ book.id|e }}">{{ book.title|e }}</a></h3>
</article>
{% endfor %}

View File

@ -9,7 +9,7 @@
<title>
{% block title %}
Библеотека Сурикова
Библиотека Сурикова
{% endblock %}
</title>
</head>

View File

@ -1,7 +1,7 @@
<header>
<header class="unselectable">
<section class="menu">
<nav>
<a class="link" href="/surikov" title="Архивный фонд">Кеменев</a>
<a class="link" href="/surikov" title="Архивный фонд">Кеменов</a>
<a class="link" href="/kemenev" title="Список книг">Суриков</a>
<a id="logo" href="/" title="Главная страница">
<img src="/img/surikovlib_logo_1_white.svg">

View File

@ -1,8 +0,0 @@
<script src="https://vk.com/js/api/openapi.js?169" type="text/javascript"></script>
<script type="text/javascript" src="https://vk.com/js/api/openapi.js?169"></script>
<div id="group"></div>
<script type="text/javascript">
VK.Widgets.Group("group", { mode: 4, wide: 0, width: parseInt(getComputedStyle(document.getElementsByTagName('aside')[0]).getPropertyValue('width')), height: "600", color1: 'd9b5b5', color2: '000', color3: '86781C' }, 29605269);
</script>