From 30fd8f0ec396196178f4d6b58d0863bd7415b499 Mon Sep 17 00:00:00 2001 From: Arsen Mirzaev Tatyano-Muradovich Date: Sat, 16 Apr 2022 17:30:06 +1000 Subject: [PATCH] =?UTF-8?q?=D1=81=D1=83=D0=BF=D0=B5=D1=80=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BF=D0=B0=20(=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/accounts_controller.php | 22 +- .../system/controllers/books_controller.php | 128 ++---- .../system/controllers/main_controller.php | 5 +- .../system/models/accounts_model.php | 381 +++++++++--------- .../surikovlib/system/models/books_model.php | 265 +++++++++++- mirzaev/surikovlib/system/public/css/auth.css | 10 + .../surikovlib/system/public/css/books.css | 76 +++- mirzaev/surikovlib/system/public/css/main.css | 23 +- mirzaev/surikovlib/system/views/auth.html | 12 +- .../surikovlib/system/views/books/book.html | 17 +- .../surikovlib/system/views/books/index.html | 8 +- mirzaev/surikovlib/system/views/core.html | 2 +- mirzaev/surikovlib/system/views/header.html | 4 +- mirzaev/surikovlib/system/views/vk.1.html | 8 - 14 files changed, 623 insertions(+), 338 deletions(-) delete mode 100644 mirzaev/surikovlib/system/views/vk.1.html diff --git a/mirzaev/surikovlib/system/controllers/accounts_controller.php b/mirzaev/surikovlib/system/controllers/accounts_controller.php index f3b250c..396584b 100644 --- a/mirzaev/surikovlib/system/controllers/accounts_controller.php +++ b/mirzaev/surikovlib/system/controllers/accounts_controller.php @@ -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; } // Генерация ответа diff --git a/mirzaev/surikovlib/system/controllers/books_controller.php b/mirzaev/surikovlib/system/controllers/books_controller.php index eeaafb5..60433b5 100644 --- a/mirzaev/surikovlib/system/controllers/books_controller.php +++ b/mirzaev/surikovlib/system/controllers/books_controller.php @@ -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); } } diff --git a/mirzaev/surikovlib/system/controllers/main_controller.php b/mirzaev/surikovlib/system/controllers/main_controller.php index 3ce6dec..726b397 100644 --- a/mirzaev/surikovlib/system/controllers/main_controller.php +++ b/mirzaev/surikovlib/system/controllers/main_controller.php @@ -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); diff --git a/mirzaev/surikovlib/system/models/accounts_model.php b/mirzaev/surikovlib/system/models/accounts_model.php index 68203d9..decc7a5 100644 --- a/mirzaev/surikovlib/system/models/accounts_model.php +++ b/mirzaev/surikovlib/system/models/accounts_model.php @@ -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(), diff --git a/mirzaev/surikovlib/system/models/books_model.php b/mirzaev/surikovlib/system/models/books_model.php index 2accf4f..41184cf 100644 --- a/mirzaev/surikovlib/system/models/books_model.php +++ b/mirzaev/surikovlib/system/models/books_model.php @@ -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; } } diff --git a/mirzaev/surikovlib/system/public/css/auth.css b/mirzaev/surikovlib/system/public/css/auth.css index cbbc18e..4dd01c8 100644 --- a/mirzaev/surikovlib/system/public/css/auth.css +++ b/mirzaev/surikovlib/system/public/css/auth.css @@ -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; +} diff --git a/mirzaev/surikovlib/system/public/css/books.css b/mirzaev/surikovlib/system/public/css/books.css index 151e35d..9bd635b 100644 --- a/mirzaev/surikovlib/system/public/css/books.css +++ b/mirzaev/surikovlib/system/public/css/books.css @@ -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; +} diff --git a/mirzaev/surikovlib/system/public/css/main.css b/mirzaev/surikovlib/system/public/css/main.css index 36017c1..42b0542 100644 --- a/mirzaev/surikovlib/system/public/css/main.css +++ b/mirzaev/surikovlib/system/public/css/main.css @@ -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; diff --git a/mirzaev/surikovlib/system/views/auth.html b/mirzaev/surikovlib/system/views/auth.html index d04af42..f6c1f19 100644 --- a/mirzaev/surikovlib/system/views/auth.html +++ b/mirzaev/surikovlib/system/views/auth.html @@ -2,15 +2,15 @@ {% if account is not empty %} -

Аккаунт

-
-

Почта: {{ account.email }}

- Выход +

Аккаунт

+
+

Почта:{{ account.mail }}

+ Выход
{% else %} -

Аутентификация

+

Аутентификация

- +
diff --git a/mirzaev/surikovlib/system/views/books/book.html b/mirzaev/surikovlib/system/views/books/book.html index 786df82..3f5ab0a 100644 --- a/mirzaev/surikovlib/system/views/books/book.html +++ b/mirzaev/surikovlib/system/views/books/book.html @@ -3,9 +3,16 @@ {% block main %} -
- Обложка книги "{{ book.title|e }}" -

{{ book.title|e }}

-

{{ book.description|e|length|trim(' ') > 80 ? book.description|e|slice(0, 80)|trim(' ') ~ '...' : book.description|e}}

-
+
+

{{ book.title|e }}

+ Страница отсутствует + +
{% endblock %} diff --git a/mirzaev/surikovlib/system/views/books/index.html b/mirzaev/surikovlib/system/views/books/index.html index 67b8f1f..7c40247 100644 --- a/mirzaev/surikovlib/system/views/books/index.html +++ b/mirzaev/surikovlib/system/views/books/index.html @@ -5,13 +5,17 @@
- + {% if account is not empty %} + {% if account.permissions.books is defined and account.permissions.books == 1 %} +

+

+ {% endif %} + {% endif %} {% for book in books %} {% endfor %} diff --git a/mirzaev/surikovlib/system/views/core.html b/mirzaev/surikovlib/system/views/core.html index 49979f2..bc1c0ba 100644 --- a/mirzaev/surikovlib/system/views/core.html +++ b/mirzaev/surikovlib/system/views/core.html @@ -9,7 +9,7 @@ {% block title %} - Библеотека Сурикова + Библиотека Сурикова {% endblock %} diff --git a/mirzaev/surikovlib/system/views/header.html b/mirzaev/surikovlib/system/views/header.html index e014806..6820c5d 100644 --- a/mirzaev/surikovlib/system/views/header.html +++ b/mirzaev/surikovlib/system/views/header.html @@ -1,7 +1,7 @@ -
+