diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/composer.json b/composer.json old mode 100644 new mode 100755 index 0a56bd8..fa1d347 --- a/composer.json +++ b/composer.json @@ -1,25 +1,26 @@ { - "name": "mirzaev/surikovlib", - "description": "Онлайн библеотека музея имени Сурикова", - "type": "project", - "license": "AGPL-3.0-or-later", - "homepage": "https://git.hood.su/mirzaev/surikovlib", - "authors": [ - { - "name": "Arsen Mirzaev Tatyano-Muradovich", - "email": "arsen@mirzaev.sexy", - "homepage": "https://mirzaev.sexy", - "role": "Developer" - } - ], - "require": { - "php": "^8.0.0", - "mirzaev/minimal": "^2.0.x-dev", - "twig/twig": "^3.3" - }, - "autoload": { - "psr-4": { - "mirzaev\\surikovlib\\": "mirzaev/surikovlib/system" - } - } + "name": "kodorvan/surikov", + "description": "Online library of the Surikov Museum", + "type": "project", + "license": "WTFPL", + "homepage": "https://git.svoboda.works/kodorvan/surikov", + "authors": [ + { + "name": "Arsen Mirzaev Tatyano-Muradovich", + "email": "arsen@mirzaev.sexy", + "homepage": "https://mirzaev.sexy", + "role": "Developer" + } + ], + "require": { + "php": "^8.4.0", + "mirzaev/minimal": "^3.6.2", + "twig/twig": "^3.3", + "mirzaev/languages": "^1.0" + }, + "autoload": { + "psr-4": { + "kodorvan\\surikov\\": "kodorvan/surikov/system" + } + } } diff --git a/composer.lock b/composer.lock old mode 100644 new mode 100755 index 7e1eada..e196ea7 --- a/composer.lock +++ b/composer.lock @@ -4,21 +4,59 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1c826a114d11e8301a0f17171d987459", + "content-hash": "97cd0861246168b4da62751f2317bc62", "packages": [ { - "name": "mirzaev/minimal", - "version": "2.0.x-dev", + "name": "mirzaev/languages", + "version": "1.0.2", "source": { "type": "git", - "url": "https://git.hood.su/mirzaev/minimal", - "reference": "7777d7af1733d661a36551a0fdcf27a972e4ef81" + "url": "https://git.svoboda.works/mirzaev/languages", + "reference": "eceff49204c718243f24e3da42294c5ea5b29e01" }, "require": { - "php": "~8.0" + "php": "^8.4" }, - "suggest": { - "ext-PDO": "Для работы с базами данных на SQL (MySQL, PostreSQL...)" + "type": "library", + "autoload": { + "psr-4": { + "mirzaev\\languages\\": "mirzaev/languages/system" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Arsen Mirzaev Tatyano-Muradovich", + "email": "arsen@mirzaev.sexy", + "homepage": "https://mirzaev.sexy", + "role": "Creator" + } + ], + "description": "Library for easy languages support", + "homepage": "https://git.svoboda.works/mirzaev/languages", + "keywords": [ + "enumeration", + "languages" + ], + "support": { + "issues": "https://git.svoboda.works/mirzaev/languages/issues", + "wiki": "https://git.svoboda.works/mirzaev/languages/wiki" + }, + "time": "2025-08-21T14:50:06+00:00" + }, + { + "name": "mirzaev/minimal", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://git.svoboda.works/mirzaev/minimal", + "reference": "d9e4e0af6cffc169831eec798d00e53187839b8e" + }, + "require": { + "php": "~8.4" }, "type": "framework", "autoload": { @@ -35,37 +73,105 @@ "name": "Arsen Mirzaev Tatyano-Muradovich", "email": "arsen@mirzaev.sexy", "homepage": "https://mirzaev.sexy", - "role": "Developer" + "role": "Programmer" } ], - "description": "Легковесный MVC фреймворк который следует твоим правилам, а не диктует свои", - "homepage": "https://git.hood.su/mirzaev/minimal", + "description": "My vision of a good framework", + "homepage": "https://git.mirzaev.sexy/mirzaev/minimal", "keywords": [ "framework", + "lightweight", "mvc" ], "support": { - "docs": "https://git.hood.su/mirzaev/minimal/manual", - "issues": "https://git.hood.su/mirzaev/minimal/issues" + "docs": "https://git.mirzaev.sexy/mirzaev/minimal/wiki", + "issues": "https://git.mirzaev.sexy/mirzaev/minimal/issues" }, - "time": "2022-03-03T21:15:52+00:00" + "time": "2025-07-16T01:09:25+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.25.0", + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "30885182c981ab175d4d034db0f6f469898070ab" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", - "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -75,12 +181,9 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -114,7 +217,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -125,29 +228,34 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-10-20T20:35:02+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { - "php": ">=7.1" + "ext-iconv": "*", + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -157,12 +265,9 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -197,7 +302,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -208,43 +313,50 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-11-30T18:21:41+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "twig/twig", - "version": "v3.3.8", + "version": "v3.21.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "972d8604a92b7054828b539f2febb0211dd5945c" + "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/972d8604a92b7054828b539f2febb0211dd5945c", - "reference": "972d8604a92b7054828b539f2febb0211dd5945c", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/285123877d4dd97dd7c11842ac5fb7e86e60d81d", + "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1.0", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { - "psr/container": "^1.0", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + "phpstan/phpstan": "^2.0", + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.3-dev" - } - }, "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], "psr-4": { "Twig\\": "src/" } @@ -277,7 +389,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.3.8" + "source": "https://github.com/twigphp/Twig/tree/v3.21.1" }, "funding": [ { @@ -289,20 +401,18 @@ "type": "tidelift" } ], - "time": "2022-02-04T06:59:48+00:00" + "time": "2025-05-03T07:21:55+00:00" } ], "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "mirzaev/minimal": 20 - }, + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^8.0.0" + "php": "^8.4.0" }, - "platform-dev": [], - "plugin-api-version": "2.2.0" + "platform-dev": {}, + "plugin-api-version": "2.6.0" } diff --git a/kodorvan/surikov/system/controllers/accounts.php b/kodorvan/surikov/system/controllers/accounts.php new file mode 100755 index 0000000..98d973d --- /dev/null +++ b/kodorvan/surikov/system/controllers/accounts.php @@ -0,0 +1,283 @@ + + */ +final class accounts extends core +{ + /** + * Errors + * + * @var array $errors Registry of errors + */ + protected array $errors = [ + 'system' => [], + 'account' => [] + ]; + + /** + * Account page + * + * @return null + */ + public function index(): null + { + if (str_contains($this->request->headers['accept'] ?? '', content::any->value)) { + // Request for any response + + // Render page + $page = $this->view->render('pages/account/index.html'); + + // Sending response + $this->response + ->status(status::ok) + ->start() + ->clean() + ->sse() + ->write($page) + ->validate($this->request) + ?->body() + ->end(); + + // Deinitializing rendered page + unset($page); + + // Exit (success) + return null; + } + + // Exit (fail) + return null; + } + + /** + * Registration + * + * @param string|null $mail Mail + * @param string|null $password Password + * @param int|string|bool|null $remember Remember + * @param string|null $redirect Redirect @deprecated + * + * @return null + */ + public function registration( + ?string $mail = null, + ?string $password = null, + int|string|bool|null $remember = null, + ?string $redirect = null + ): null { + if ($this->view->account = account::registrate(mail: $mail, password: $password, errors: $this->errors['account'])) { + // Registered + + // Filtering the remember argument + $remember = (bool) $remember; + + if ($this->view->account = account::authenticate(mail: $mail, password: $password, remember: $remember, errors: $this->errors['account'])) { + // Authenticated + } else { + // Not authenticated + + // Sending response + $this->response + ->status(status::forbidden) + ->start() + ->clean() + ->sse() + ->validate($this->request) + ?->body() + ->end(); + } + } else { + // Not registered +var_dump($this->errors); die; + // Sending response + $this->response + ->status(status::unauthorized) + ->start() + ->clean() + ->sse() + ->validate($this->request) + ?->body() + ->end(); + } + + // Initializing path to redirecting @deprecated + $redirect ??= $_SERVER['HTTP_REFERER'] ?? '/'; + + // Redirecting + header("Location: $redirect", response_code: status::see_other->value); + + // Exit (success) + return null; + } + + /** + * Authentication + * + * @param string|null $mail Mail + * @param string|null $password Password + * @param int|string|bool|null $remember Remember + * @param string|null $redirect Redirect @deprecated + * + * @return null + */ + public function authentication( + ?string $mail = null, + ?string $password = null, + int|string|bool|null $remember = null, + ?string $redirect = null + ): ?string { + // Filtering the remember argument + $remember = (bool) $remember; + + if ($this->view->account = account::authenticate(mail: $mail, password: $password, remember: $remember, errors: $this->errors['account'])) { + // Authenticated + } else { + // Not authenticated + + // Sending response + $this->response + ->status(status::forbidden) + ->start() + ->clean() + ->sse() + ->validate($this->request) + ?->body() + ->end(); + } + + // Initializing path to redirecting @deprecated + $redirect ??= $_SERVER['HTTP_REFERER'] ?? '/'; + + // Redirecting + header("Location: $redirect", response_code: status::see_other->value); + + // Exit (success) + return null; + } + + /** + * Deauthentication + * + * @return null + */ + public function deauthentication(): null + { + if (account::deauthenticate(errors: $this->errors['account'])) { + // Deauthenticated + } else { + // Not deauthenticated + + // Sending response + $this->response + ->status(status::internal_server_error) + ->start() + ->clean() + ->sse() + ->validate($this->request) + ?->body() + ->end(); + } + + // Перенаправление + header('Location: /', response_code: status::see_other->value); + + // Exit (success) + return null; + } + + /** + * Data + * + * If the information requested is not by an administrator, + * only publicly permitted information will be returned. + * + * @param int|null $identifier Identifier + * + * @return string JSON-документ + */ + public function data(?int $identifier = null): ?string + { + if ($account = account::read(expressions: ['identifier' => $identifier], errors: $this->errors['account'])) { + // Found the account + + // Initializing the account + $this->view->account = account::initialize(errors: $this->errors['account']); + + if ($this->view->account) { + // Initialized the account + + if ($this->view->account->permissions['accounts'] ?? 0 === 1) { + // Authorized to accounts + } else { + // Not authorized to accounts + + // Deinitializing the account private properties + unset($account->password, $account->hash, $account->time); + } + + if (str_contains($this->request->headers['accept'] ?? '', content::any->value)) { + // Request for any response + + // Render page + $page = $this->view->render('pages/account/index.html'); + + // Sending response + $this->response + ->status(status::ok) + ->start() + ->clean() + ->sse() + ->json($account) + ->validate($this->request) + ?->body() + ->end(); + + // Deinitializing rendered page + unset($page); + + // Exit (success) + return null; + } + } + } else { + // Not found the account + + // Sending response + $this->response + ->status(status::not_found) + ->start() + ->clean() + ->sse() + ->validate($this->request) + ?->body() + ->end(); + } + + // Exit (success) + return null; + } +} diff --git a/kodorvan/surikov/system/controllers/books.php b/kodorvan/surikov/system/controllers/books.php new file mode 100755 index 0000000..95293e6 --- /dev/null +++ b/kodorvan/surikov/system/controllers/books.php @@ -0,0 +1,313 @@ + + */ +final class books extends core +{ + /** + * Errors + * + * @var array $errors Registry of errors + */ + protected array $errors = [ + 'system' => [], + 'account' => [], + 'books' => [] + ]; + + /** + * Books page (or the book page) + * + * @param int|string|null $identifier Identifier of the book + * @param int|string $page Page if the book + * + * @return null + */ + public function index( + int|string|null $identifier = null, + int|string $page = 1 + ): null { + // Normalizing the page argument + $page = (int) $page; + if ($page < 1) $page = 1; + + if (isset($identifier)) { + // Received the book identifier (the book) + + // Initializing the book + $this->view->book = book::read(expressions: ['identifier' => (int) $identifier])[0] ?? null; + + if (empty($page)) { + // Received the book page + + // Initializing the book page + $this->view->page = $page; + } + + if (str_contains($this->request->headers['accept'] ?? '', content::any->value)) { + // Request for any response + + // Render page + $page = $this->view->render('books/book.html'); + + // Sending response + $this->response + ->start() + ->clean() + ->sse() + ->write($page) + ->validate($this->request) + ?->body() + ->end(); + + // Deinitializing rendered page + unset($page); + + // Exit (success) + return null; + } + } else { + // Not received the book identifier (all books) + + // Reading books + $this->view->books = book::read(limit: 30, page: $page); + + if (str_contains($this->request->headers['accept'] ?? '', content::any->value)) { + // Request for any response + + // Render page + $page = $this->view->render('books/index.html'); + + // Sending response + $this->response + ->start() + ->clean() + ->sse() + ->write($page) + ->validate($this->request) + ?->body() + ->end(); + + // Deinitializing rendered page + unset($page); + + // Exit (success) + return null; + } + } + + // Exit (fail) + return null; + } + + /** + * Write + * + * Import books into the storage and the database + * + * @return null + */ + public function write(): null + { + if (account::initialize(errors: $this->errors['account'])->access('books')) { + // Initialized account and authorized to books + + if (count($books = book::import(files: $this->request->files['books'] ?? [], errors: $this->errors['books'])) > 0) { + // Imported books + } else { + // Not imported books + } + } + + // Redirecting + header(header: 'Location: /books', response_code: status::see_other->value); + + // Exit (success) + return null; + } + + /** + * Read + * + * Read the book page file (expected jpeg image) + * + * @param int|string|null $identifier Identifier of the book + * @param int|string|null $page Page of the book + * + * @return string|null The book page file + */ + public function read( + int|string|null $identifier = null, + int|string|null $page = 1 + ): ?string { + // Normalizing the identifier argument + $identifier = (int) $identifier; + + // Normalizing the page argument + $page = (int) $page; + if ($page < 1) $page = 1; + + // Initializing the book file path + $file = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $identifier . DIRECTORY_SEPARATOR . $page . '.jpg'; + + if (file_exists($file)) { + // Found the book page file + + // Initializing headers + 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)); + + // Cleaning the output buffer + ob_end_clean(); + + // Exit (success) + return file_get_contents($file); + } + + // Exit (fail) + return null; + } + + /** + * Delete + * + * Delete the book from the storage and the database + * + * @param int|string|null $identifier Identifier of the book + * + * @return null + */ + public function delete(int|string|null $identifier = null): null + { + if (account::initialize(errors: $this->errors['account'])->access('books')) { + // Initialized account and authorized to books + + // Initializing the processing status + $status = false; + + // Normalizing the identifier argument + $identifier = (int) $identifier; + + if (book::delete(identifier: $identifier, errors: $this->errors['books'])) { + // Deleted the book from the database + + // Initializing the book directory path + $directory = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $identifier; + + if (file_exists($directory)) { + // Found the book directory + + // Deletimg the book from the storage + exec('rm -rf ' . escapeshellarg($directory)); + + // Writing the processing status + $status = true; + } + } + + if (str_contains($this->request->headers['accept'] ?? '', content::json->value)) { + // Request for JSON response + + // Sending response + $this->response + ->start() + ->clean() + ->sse() + ->json([ + 'status' => $status, + 'errors' => $this->errors + ]) + ->validate($this->request) + ?->body() + ->end(); + + // Exit (success) + return null; + } + } + + // Exit (fail) + return null; + } + + /** + * Rotate + * + * Rotate the book image in the storage + * + * @param int|string|null $identifier Identifier of the book + * @param int|string|null $page Page of the book + * + * @return null + */ + public function rotate( + int|string|null $identifier = null, + int|string|null $page = 1 + ): null { + if (account::initialize(errors: $this->errors['account'])->access('books')) { + // Initialized account and authorized to books + + // Normalizing the identifier argument + $identifier = (int) $identifier; + + // Normalizing the page argument + $page = (int) $page; + + // Rotating the book page file + $status = book::rotate(identifier: $identifier, page: $page, errors: $this->errors['books']); + + if (str_contains($this->request->headers['accept'] ?? '', content::json->value)) { + // Request for JSON response + + // Sending response + $this->response + ->start() + ->clean() + ->sse() + ->json([ + 'status' => $status, + 'errors' => $this->errors + ]) + ->validate($this->request) + ?->body() + ->end(); + + // Exit (success) + return null; + } + } + + // Exit (fail) + return null; + } +} diff --git a/kodorvan/surikov/system/controllers/contacts.php b/kodorvan/surikov/system/controllers/contacts.php new file mode 100755 index 0000000..5bddf58 --- /dev/null +++ b/kodorvan/surikov/system/controllers/contacts.php @@ -0,0 +1,72 @@ + + */ +final class contacts extends core +{ + /** + * Errors + * + * @var array $errors Registry of errors + */ + protected array $errors = [ + 'system' => [], + 'account' => [] + ]; + + /** + * Contacts page + * + * @return null + */ + public function index(): null + { + if (str_contains($this->request->headers['accept'] ?? '', content::any->value)) { + // Request for any response + + // Render page + $page = $this->view->render('pages/contacts/index.html'); + + // Sending response + $this->response + ->start() + ->clean() + ->sse() + ->write($page) + ->validate($this->request) + ?->body() + ->end(); + + // Deinitializing rendered page + unset($page); + + // Exit (success) + return null; + } + + // Exit (fail) + return null; + } +} diff --git a/kodorvan/surikov/system/controllers/core.php b/kodorvan/surikov/system/controllers/core.php new file mode 100755 index 0000000..0c55965 --- /dev/null +++ b/kodorvan/surikov/system/controllers/core.php @@ -0,0 +1,88 @@ + + */ +class core extends controller +{ + /** + * Language + * + * @var language $language Language + */ + protected language $language = language::ru; + + /** + * Response + * + * @see https://wiki.php.net/rfc/property-hooks (find a table about backed and virtual hooks) + * + * @var response $response Response + */ + protected response $response { + // Read + get => $this->response ??= $this->request->response(); + } + + /** + * Errors + * + * @var array $errors Registry of errors + */ + protected array $errors = [ + 'system' => [], + 'account' => [] + ]; + + /** + * Constructor + * + * @param minimal $core Instance of the MINIMAL + * @param bool $initialize Initialize a controller? + * + * @return void + */ + public function __construct(minimal $core) + { + // Blocking requests from CloudFlare (better to write this blocking into nginx config file) + if (isset($_SERVER['HTTP_USER_AGENT']) && $_SERVER['HTTP_USER_AGENT'] === 'nginx-ssl early hints') return status::bruh->label; + + // Initializing the models core + new models(); + + // Initializing the view template engine instance + $this->view = new templater(account::initialize(errors: $this->errors['account'])); + + // For the extends system + parent::__construct(core: $core); + } +} diff --git a/kodorvan/surikov/system/controllers/errors.php b/kodorvan/surikov/system/controllers/errors.php new file mode 100755 index 0000000..9f30b12 --- /dev/null +++ b/kodorvan/surikov/system/controllers/errors.php @@ -0,0 +1,45 @@ + + */ +final class errors_controller extends core +{ + /** + * Errors + * + * @var array $errors Registry of errors + */ + protected array $errors = [ + 'system' => [], + 'account' => [] + ]; + + public function error404(): ?string + { + // Генерация представления + return 'Ошибка 404 (не найдено)'; + } + + public function error500(): ?string + { + // Генерация представления + return 'Ошибка 500 (на стороне сервера)'; + } +} diff --git a/kodorvan/surikov/system/controllers/index.php b/kodorvan/surikov/system/controllers/index.php new file mode 100755 index 0000000..0d18462 --- /dev/null +++ b/kodorvan/surikov/system/controllers/index.php @@ -0,0 +1,72 @@ + + */ +final class index extends core +{ + /** + * Errors + * + * @var array $errors Registry of errors + */ + protected array $errors = [ + 'system' => [], + 'account' => [] + ]; + + /** + * Main page + * + * @return null + */ + public function index(): null + { + if (str_contains($this->request->headers['accept'] ?? '', content::any->value)) { + // Request for any response + + // Render page + $page = $this->view->render('main/index.html'); + + // Sending response + $this->response + ->start() + ->clean() + ->sse() + ->write($page) + ->validate($this->request) + ?->body() + ->end(); + + // Deinitializing rendered page + unset($page); + + // Exit (success) + return null; + } + + // Exit (fail) + return null; + } +} diff --git a/kodorvan/surikov/system/controllers/kemenov.php b/kodorvan/surikov/system/controllers/kemenov.php new file mode 100755 index 0000000..97c81bf --- /dev/null +++ b/kodorvan/surikov/system/controllers/kemenov.php @@ -0,0 +1,74 @@ + + */ +final class kemenov extends core +{ + /** + * Errors + * + * @var array $errors Registry of errors + */ + protected array $errors = [ + 'system' => [], + 'account' => [] + ]; + + /** + * Kemenov page + * + * @return null + */ + public function index(): null + { + if (str_contains($this->request->headers['accept'] ?? '', content::any->value)) { + // Request for any response + + // Render page + $page = $this->view->render('pages/kemenov/index.html'); + + // Sending response + $this->response + ->start() + ->clean() + ->sse() + ->write($page) + ->validate($this->request) + ?->body() + ->end(); + + // Deinitializing rendered page + unset($page); + + // Exit (success) + return null; + } + + // Exit (fail) + return null; + } +} diff --git a/kodorvan/surikov/system/controllers/surikov.php b/kodorvan/surikov/system/controllers/surikov.php new file mode 100755 index 0000000..19e7f2d --- /dev/null +++ b/kodorvan/surikov/system/controllers/surikov.php @@ -0,0 +1,72 @@ + + */ +final class surikov extends core +{ + /** + * Errors + * + * @var array $errors Registry of errors + */ + protected array $errors = [ + 'system' => [], + 'account' => [] + ]; + + /** + * Surikov page + * + * @return null + */ + public function index(): null + { + if (str_contains($this->request->headers['accept'] ?? '', content::any->value)) { + // Request for any response + + // Render page + $page = $this->view->render('pages/surikov/index.html'); + + // Sending response + $this->response + ->start() + ->clean() + ->sse() + ->write($page) + ->validate($this->request) + ?->body() + ->end(); + + // Deinitializing rendered page + unset($page); + + // Exit (success) + return null; + } + + // Exit (fail) + return null; + } +} diff --git a/kodorvan/surikov/system/models/account.php b/kodorvan/surikov/system/models/account.php new file mode 100755 index 0000000..d86eb40 --- /dev/null +++ b/kodorvan/surikov/system/models/account.php @@ -0,0 +1,711 @@ + + */ +final class account extends core +{ + /** + * Identifier + * + * @var int $identifier + */ + public int $identifier; + + /** + * Mail + * + * @var string $mail + */ + public string $mail; + + /** + * Password + * + * @var string $password + */ + public string $password; + + /** + * Hash + * + * Hash of the session + * + * @var string|null $hash + */ + public ?string $hash = null; + + /** + * Time + * + * Time of the session + * + * @var int|null $time + */ + public ?int $time = null; + + /** + * Permissions + * + * @var array $permissions + */ + public array $permissions; + + /** + * Constructor + * + * @param array $parameters Parameters + * + * @return void + */ + public function __construct(array $parameters) + { + foreach ($parameters as $key => $value) { + // Iterating over parameters + + // Writing the property + if (property_exists($this, $key)) $this->$key = $value; + } + } + + /** + * Registrate + * + * @param string $mail Mail + * @param string $password Password + * @param bool $authenticate Authenticate the account after registration? + * @param array &$errors Registry of errors + * + * @return static|null Account + */ + public static function registrate(string $mail, string $password, bool $authenticate = true, array &$errors = []): ?static + { + try { + if (static::initialize(errors: $errors)) { + // Authenticated the account + + // Exit (fail) + throw new exception('Уже аутентифицирован'); + } + + if (empty($account = static::read(expressions: ['mail' => $mail]))) { + // Not found the account + + if (static::write(mail: $mail, password: $password, errors: $errors)) { + // Registrated the account + + if ($authenticate) { + // Requested authentication after registration + + // Authentication + $account = static::authenticate(mail: $mail, password: $password, remember: true, errors: $errors); + } + + // Exit (success) + return $account; + } + } else { + // Found the account + + // Exit (success) + return $account; + } + } catch (exception $e) { + // Exception + + // Writing into the errors registry + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } + + /** + * Authenticate + * + * @param string $mail Mail + * @param string $password Password + * @param bool $remember Increase time of the session? + * @param array &$errors Registry of errors + * + * @return static|null Account + */ + public static function authenticate(string $mail, string $password, bool $remember = false, array &$errors = []): ?static + { + try { + if (static::initialize(errors: $errors)) { + // Authenticated the account + + // Exit (fail) + throw new exception('Уже аутентифицирован'); + } + + if (empty($account = static::read(expressions: ['mail' => $mail]))) { + // Not found the account + + // Exit (fail) + throw new exception('Не удалось найти аккаунт'); + } + + if (password_verify($password, $account->password)) { + // Matches hashes + + // Initializing the session identifier + session_id((string) $account->identifier); + + // Initializing the session name + session_name('identifier'); + + // Initializing the session + session_start(); + + // Calculating the session time + $time = time() + ($remember ? 604800 : 86400); + + // Generating the session hash + $hash = static::hash(identifier: (int) $account->identifier, hash: crypt($account->password, time() . $account->identifier), time: $time, errors: $errors)['hash']; + + // Initializing cookies + setcookie(name: 'hash', value: $hash, expires_or_options: $time, path: '/', secure: true); + + // Exit (success) + return $account; + } else { + // Not matches hashes + + // Exit (fail) + throw new exception('Wrong password'); + } + } catch (exception $e) { + // Exception + + // Writing into the errors registry + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } + + /** + * Deauthenticate + * + * @param array &$errors Registry of errors + * + * @return bool The session is deauthenticated? + */ + public static function deauthenticate(array &$errors = []): bool + { + try { + if ($account = static::initialize(errors: $errors)) { + // Authenticated the account + + // Initializing the request + $request = static::$database->prepare("UPDATE `accounts` SET `hash` = null, `time` = 0 WHERE `identifier` = :identifier"); + + // Initializing parameters of the request + $parameters = [ + ":identifier" => $account->identifier, + ]; + + // Sending the request + $request->execute($parameters); + + // Generating the answer + $request->fetch(pdo::FETCH_ASSOC); + + // Deinitializing cookies + setcookie('identifier', '', 0, path: '/', secure: true); + setcookie('hash', '', 0, path: '/', secure: true); + + // Exit (fail) + return true; + } else { + // Not authenticated the account + + // Exit (fail) + throw new exception('Not authenticated'); + } + } catch (exception $e) { + // Exception + + // Writing into the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return false; + } + + /** + * Initializing + * + * Initializing of the account + * + * @param int|null $identifier Identifier of the account + * @param array &$errors Registry of errors + * + * @return static|null Account + * + * @deprecated + */ + public static function init(?int $identifier = null, array &$errors = []): ?static + { + // Exit (success/fail) + return static::initialize(identifier: $identifier, errors: $errors); + } + + /** + * Initialize + * + * Initializing of the account + * + * @param int|null $identifier Identifier of the account + * @param array &$errors Registry of errors + * + * @return static|null Account + */ + public static function initialize(?int $identifier = null, array &$errors = []): ?static + { + try { + if (isset($identifier)) { + // Recaifer identifier of the account + + if (empty($account = static::read(['identifier' => $identifier]))) { + // Found the account + + // Exit (fail) + throw new exception('Не найден пользователь'); + } + } else if (!empty($_COOKIE['identifier']) && !empty($_COOKIE['hash'])) { + // Found cookie with the account data (expected that is already authenticated) + + if ($_COOKIE['hash'] === static::hash(identifier: (int) $_COOKIE['identifier'], errors: $errors)['hash']) { + // Matches hashes from the cookie and the database + } else { + // Not matches hashes from the cookie and the database + + // Exit (fail) + throw new exception('Вы аутентифицированы с другого устройства (не совпадают хеши аутентификации)'); + } + + if (empty($account = static::read([ + 'identifier' => $_COOKIE['identifier'], + 'hash' => $_COOKIE['hash'] + ]))) { + // Not found the account or a connection with it + + // Exit (fail) + throw new exception('Не найден пользователь или время аутентификации истекло'); + } + } else { + // Not found parameters for authentication + + // Exit (fail) + return null; + } + + // Initializing permissions + $account->permissions = static::permissions(identifier: (int) $account->identifier, errors: $errors); + + // Exit (success) + return $account; + } catch (exception $e) { + // Exception + + // Writing into the errors registry + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } + + /** + * Permissions + * + * Read permissions of the account from the database + * + * @param int $identifier Identifier of the account + * @param array &$errors Registy of errors + * + * @return array Permissions of the account + */ + public static function permissions(int $identifier, array &$errors = []): array + { + try { + // Initializing the request + $request = static::$database->prepare("SELECT * FROM `permissions` WHERE `account` = :identifier"); + + // Initializing parametes of the request + $parameters = [ + ":identifier" => $identifier + ]; + + // Sending the request + $request->execute($parameters); + + // Generating the answer + if (empty($response = $request->fetch(pdo::FETCH_ASSOC))) { + // Not found permissions + + // Exit (fail) + throw new exception('Not found permissions'); + } + + // Deleting deprecated parameters + unset($response['identifier']); + + // Exit (success) + return $response; + } catch (exception $e) { + // Exception + + // Writing into the errors registry + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return []; + } + + /** + * Access + * + * Check for the account authorizing by the permission + * + * @param string $permission Permission + * @param array &$errors Registry of errors + * + * @return bool|null Authorized? + */ + public function access(string $permission, array &$errors = []): ?bool + { + try { + // Exit (success) + return isset($this->permissions[$permission]) ? (bool) $this->permissions[$permission] : null; + } catch (exception $e) { + // Exception + + // Writing into the errors registry + $errors['account'][] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } + + /** + * Write + * + * Write the account into the database + * + * @param string $mail Mail + * @param string $password Password + * @param array &$errors Registry of errors + * + * @return static|null Account + */ + public static function write(string $mail, string $password, array &$errors = []): ?static + { + try { + // Initializing parameters of the request + $parameters = []; + + // Filtering the parameter + if (filter_var($mail, FILTER_VALIDATE_EMAIL) === false) throw new exception('Не удалось распознать почту'); + if (iconv_strlen($mail) < 3) throw new exception('Длина почты должна быть не менее 3 символов'); + if (iconv_strlen($mail) > 60) throw new exception('Длина почты должна быть не более 80 символов'); + + // Writing the parameter + $parameters[':mail'] = $mail; + + // Filtering the parameter + if (iconv_strlen($password) < 3) throw new exception('Длина пароля должна быть не менее 3 символов'); + if (iconv_strlen($password) > 60) throw new exception('Длина пароля должна быть не более 120 символов'); + + // Writing the parameter + $parameters[':password'] = password_hash($password, PASSWORD_BCRYPT); + + // Initializing the request + $request = static::$database->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' : '') . ")"); + + // Sending the request + $request->execute($parameters); + + // Generating the answer + $request->fetch(pdo::FETCH_ASSOC); + + // Reading the account + $account = static::read(expressions: ['mail' => $mail]); + + if ($account instanceof static) { + // Registered the account + + // Initializing the request + $request = static::$database->prepare("INSERT INTO `permissions` (`account`) VALUES (:identifier)"); + + // Initializing parameters of the request + $parameters = [ + ':identifier' => $account->identifier + ]; + + // Sending the request + $request->execute($parameters); + + // Generating the answer + $request->fetch(pdo::FETCH_ASSOC); + + // Exit (success) + return $account; + } + } catch (exception $e) { + // Exception + + // Writing into the errors registry + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } + + /** + * Read + * + * Read the account from the database + * + * @param array $expressions Request expressions [name => value] + * @param array &$errors Registry of errors + * + * @return static|null Account + */ + public static function read(array $expressions, array &$errors = []): ?static + { + try { + // Initializing the request row + $row = 'WHERE '; + + // Declaring the request parameters + $parameters = []; + + foreach ($expressions as $name => $value) { + // Iterating over request expressions + + // Generating the request row + $row .= "`$name` = :$name &&"; + + // Generating the request parameters + $parameters[":$name"] = $value; + } + + // Cleaning the request row + $row = empty($expressions) ? '' : trim(trim($row, '&&')); + + // Initializing the request + $request = static::$database->prepare("SELECT * FROM `accounts` $row LIMIT 1"); + + // Sending the request + $request->execute($parameters); + + // Generating the response + $response = $request->fetch(pdo::FETCH_ASSOC); + + if (!empty($response) && $account = new static($response ?? [])) { + // Found the account + + if ($permissions = static::permissions(identifier: (int) $account->identifier, errors: $errors)) { + // Found the account permissions + + // Writing permissions into the account implementator + $account->permissions = $permissions; + } + + // Exit (success) + return $account; + } else { + // Not found the account + + // Exit (fail) + throw new exception('Not found the account'); + } + } catch (exception $e) { + // Exception + + // Writing into the errors registry + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } + + /** + * Hash + * + * Write or read the account hash from the database + * + * @param int $identifier Identifier + * @param int|null $hash Hash + * @param string|null $time Time + * @param array &$errors Registry of errors + * + * @return array|bool Read: ['hash' => $hash, 'time' => $time]; Write: true; Error: false + */ + public static function hash(int $identifier, string|null $hash = null, int|null $time = null, array &$errors = []): array|bool + { + try { + if (isset($hash, $time)) { + // Write (received hash and time) + + // Initializing the request + $request = static::$database->prepare("UPDATE `accounts` SET `hash` = :hash, `time` = :time WHERE `identifier` = :identifier"); + + // Initializing the request parameters + $parameters = [ + ":identifier" => $identifier, + ":hash" => $hash, + ":time" => $time, + ]; + + // Sending the request + $request->execute($parameters); + + // Generating the answer + $response = $request->fetch(pdo::FETCH_ASSOC); + + // Exit (success) + return ['hash' => $hash, 'time' => $time]; + } else { + // Read (not received hash and time) + + // Initializing the request + $request = static::$database->prepare("SELECT `hash`, `time` FROM `accounts` WHERE `identifier` = :identifier"); + + // Initializing the request parameters + $parameters = [ + ":identifier" => $identifier + ]; + + // Sending the request + $request->execute($parameters); + + // Generating the answer + extract((array) $request->fetch(pdo::FETCH_ASSOC)); + + if (!empty($response['time']) && $response['time'] <= time()) { + // Expired the hash + + // Initializing the request + $request = static::$database->prepare("UPDATE `accounts` SET `hash` = :hash, `time` = :time WHERE `identifier` = :identifier"); + + // Initializing the request parameters + $parameters = [ + ":identifier" => $identifier, + ":hash" => null, + ":time" => null, + ]; + + // Sending the request + $request->execute($parameters); + + // Generating the answer + $response = $request->fetch(pdo::FETCH_ASSOC); + + // Exit (fail) + throw new exception('Expired the hash'); + } + + // Exit (success) + return ['hash' => $hash, 'time' => $time]; + } + } catch (exception $e) { + // Exception + + // Writing into the errors registry + $errors['account'][] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return false; + } +} diff --git a/kodorvan/surikov/system/models/book.php b/kodorvan/surikov/system/models/book.php new file mode 100755 index 0000000..536b705 --- /dev/null +++ b/kodorvan/surikov/system/models/book.php @@ -0,0 +1,412 @@ + + */ +final class book extends core +{ + /** + * Write + * + * Write into the database + * + * @param string $title Title + * @param string|null $description Description + * @param int|null $account Identifier of the account + * @param array &$errors Registry of errors + * + * @return int|null The book identifier + */ + public static function write(string $title, ?string $description = null, ?int $account = null, array &$errors = []): ?int + { + try { + // Initializing the account + $account = account::initialize($account, $errors); + + // Initializing the request + $request = static::$database->prepare("INSERT INTO `books` (`account`, `title`, `description`) VALUES (:account, :title, :description)"); + + // Initializing the request parameters + $parameters = [ + ':account' => $account->identifier, + ':title' => $title, + ':description' => $description + ]; + + // Sending the request + $request->execute($parameters); + + if ($id = static::$database->lastInsertId()) { + // Received the created book identifier + + // Exit (success) + return (int) $id; + } + } catch (exception $e) { + // Exception + + // Writing into the errors registry + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } + + /** + * Read + * + * Read books from the database + * + * @param array $expressions Request expressions + * @param int $limit Amount + * @param int $page Page (offset by $limit) + * @param array &$errors Registry of errors + * + * @return array|false Books + */ + public static function read(array $expressions = [], int $limit = 1, int $page = 1, array &$errors = []): array|false + { + try { + // Initializing the request row + $row = 'WHERE '; + + // Initializing the request parameters + $parameters = []; + + foreach ($expressions as $name => $value) { + // Iterating over the request expressions + + // Generating the request row + $row .= "`$name` = :$name &&"; + + // Generating the request parameters + $parameters[":$name"] = $value; + } + + // Cleaning the request row + $row = empty($expressions) ? '' : trim(trim($row, '&&')); + + // Initializing the page + $page = $limit * --$page; + + // Initializing the request + $request = static::$database->prepare("SELECT * FROM `books` $row LIMIT $page, $limit"); + + // Sending the request + $request->execute($parameters); + + // Exit (success) + return (array) $request->fetchAll(pdo::FETCH_ASSOC); + } catch (exception $e) { + // Exception + + // Writing into the errors registry + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return false; + } + + /** + * Delete + * + * Delete the book from the database + * + * @param int $identifier Identifier + * @param array &$errors Registry of errors + * + * @return bool Is the book was deleted? + */ + public static function delete(int $identifier, array &$errors = []): bool + { + try { + // Initializing the request + $request = static::$database->prepare("DELETE FROM `books` WHERE `identifier` = :identifier LIMIT 1"); + + // Sending the request + $request->execute([':identifier' => $identifier]); + + // Exit (success) + return true; + } catch (exception $e) { + // Exception + + // Writing into the errors registry + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return false; + } + + /** + * Import + * + * Import books into the storage and database + * + * @param array $files Books + * @param int|null $account Identifier of the account + * @param array &$errors Registry of errors + * + * @return array|false Imported books + */ + public static function import(array $files, ?int $account = null, array &$errors = []): array|false + { + try { + if (empty($files)) { + // Not received books + + // Exit (fail) + throw new exception('Не найдены книги для записи'); + } + + // Initializing the account + $account = account::initialize($account, $errors); + + // Declaring the buffer of saved books + $saved = []; + + for ($i = -1; count($files['name']) > ++$i;) { + // Iterating over books + + // Generating the book file hash + $hash = hash_file('md5', $files['tmp_name'][$i]) ?? 0; + + if (move_uploaded_file($files['tmp_name'][$i], \STORAGE . DIRECTORY_SEPARATOR . 'temp' . DIRECTORY_SEPARATOR . $hash . '_' . $files['name'][$i])) { + // Saved the book file + + // Writing into the buffer of saved books + $saved[] = [ + 'name' => preg_replace('/\.pdf/', '', $files['name'])[0], + 'file' => $hash . '_' . $files['name'][$i] + ]; + } + } + + // Declaring buffer of writed books + $writed = []; + + foreach ($saved as $book) { + // Iterating over saved books + + try { + if ($identifier = static::write(title: $book['name'], description: 'Без описания', account: $account->identifier ?? null, errors: $errors)) { + // Writed book into the database + + // Initializing the book directory path + $directory = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $identifier . DIRECTORY_SEPARATOR; + + if (!file_exists($directory)) { + // Not found the book directory + + if (!mkdir($directory, 0755, true)) { + // Failed to create the book directory + + // Skipping (fail) + throw new exception('Не удалось записать директорию для книги'); + } + } + + // Initializing the book file path + $file = \STORAGE . DIRECTORY_SEPARATOR . 'temp' . DIRECTORY_SEPARATOR . $book['file']; + + // Extracting images from the PDF document + exec("pdfimages -j '$file' '$directory'"); + + // Renaming files + exec("echo 'export j=-1; for i in $directory*.jpg; do let j+=1; mv \$i $directory\$j.jpg; done' | bash"); + + // Writing into the buffer of writed books + $writed[] = $identifier; + } else { + // Not writed into the database + + // Skipping (fail) + throw new exception('Не удалось записать книгу в базу данных', 500); + } + } catch (exception $e) { + // Exception + + // Writing into the errors registry + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + } + + // Exit (success) + return $writed; + } catch (exception $e) { + // Exception + + // Writing into the errors registry + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return false; + } + + /** + * Rotate + * + * Rotate the book page image + * + * @param int $identifier Identifier + * @param int $page Page (offset) + * @param array &$errors Registry of errors + * + * @return bool Is the book page was rotated? + */ + public static function rotate(int $identifier, int $page = 1, array &$errors = []): bool + { + try { + // Initializing the request + $request = static::$database->prepare("SELECT EXISTS (SELECT * FROM `books` WHERE `identifier` = :identifier LIMIT 1)"); + + // Sending the request + $request->execute([':identifier' => $identifier]); + + if ($request->fetch(pdo::FETCH_NUM)[0] === 1) { + // Found the book + + // Initializing the book directory path + $directory = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $identifier; + + // Initializing the book file path + $file = $directory . DIRECTORY_SEPARATOR . "$page.jpg"; + + // Initializing the book file new location path + $old = $directory . DIRECTORY_SEPARATOR . 'old' . DIRECTORY_SEPARATOR . "$page.jpg"; + + if (!file_exists($directory)) { + // Not found the book directory + + // Exit (fail) + throw new exception('Не удалось найти директорию книги'); + } + + // Инициализация директории оригинальных изображений + if (!file_exists($directory . DIRECTORY_SEPARATOR . 'old')) { + // Not found the directory for deprecated files + + if (!mkdir($directory . DIRECTORY_SEPARATOR . 'old', 0755, true)) { + // Failed to create the directory for deprecated files + + // Exit (fail) + throw new exception('Failed to create the directory for deprecated files'); + } + } + + // Moving the deprecated file + exec("mv $file $old"); + + // Rotating the deprecated file and moving back + exec("jpegtran -rotate 90 $old > $file"); + + // Exit (success) + return true; + } + } catch (exception $e) { + // Exception + + // Writing into the errors registry + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return false; + } + + /** + * Amount + * + * Calculate amount of pages in the book storage + * + * @param int $identifier Identifier + * @param array &$errors Registry of errors + * + * @return int|false Amount of pages + */ + public static function amount(int $id, array &$errors = []): int|false + { + try { + // Initializing the iterator + $page = 0; + + while (true) { + // Iterating over the book pages files in ascending order (from 0.jpg to 999.jpg and more) (!!! recursion !!!) + + if (!file_exists(\STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $id . DIRECTORY_SEPARATOR . $page++ . '.jpg')) { + // Not found the book page by the iterator + + // Exit (success) (!!! exit from the recursion !!!) + return $page; + } + } + } catch (exception $e) { + // Exception + + // Writing into the errors registry + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return false; + } +} diff --git a/kodorvan/surikov/system/models/core.php b/kodorvan/surikov/system/models/core.php new file mode 100755 index 0000000..5aacb9e --- /dev/null +++ b/kodorvan/surikov/system/models/core.php @@ -0,0 +1,54 @@ + + */ +class core extends model +{ + /** + * Database + * + * @see https://www.php.net/manual/en/book.pdo.php PDO + * + * @var pdo $database Database + */ + protected static pdo $database; + + /** + * Constructor + * + * + * @return void + */ + public function __construct() + { + // Initializing the database connetion + self::$database = new pdo(DATABASE['type'] . ':dbname=' . DATABASE['name'] . ';host=' . DATABASE['host'], DATABASE['login'], DATABASE['password']); + } +} diff --git a/mirzaev/surikovlib/system/public/css/auth.css b/kodorvan/surikov/system/public/css/auth.css old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/public/css/auth.css rename to kodorvan/surikov/system/public/css/auth.css diff --git a/mirzaev/surikovlib/system/public/css/books.css b/kodorvan/surikov/system/public/css/books.css old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/public/css/books.css rename to kodorvan/surikov/system/public/css/books.css diff --git a/mirzaev/surikovlib/system/public/css/main.css b/kodorvan/surikov/system/public/css/main.css old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/public/css/main.css rename to kodorvan/surikov/system/public/css/main.css diff --git a/mirzaev/surikovlib/system/public/css/pages.css b/kodorvan/surikov/system/public/css/pages.css old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/public/css/pages.css rename to kodorvan/surikov/system/public/css/pages.css diff --git a/mirzaev/surikovlib/system/public/css/upload.css b/kodorvan/surikov/system/public/css/upload.css old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/public/css/upload.css rename to kodorvan/surikov/system/public/css/upload.css diff --git a/mirzaev/surikovlib/system/public/img/background_1.png b/kodorvan/surikov/system/public/img/background_1.png old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/public/img/background_1.png rename to kodorvan/surikov/system/public/img/background_1.png diff --git a/mirzaev/surikovlib/system/public/img/background_1.svg b/kodorvan/surikov/system/public/img/background_1.svg old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/public/img/background_1.svg rename to kodorvan/surikov/system/public/img/background_1.svg diff --git a/mirzaev/surikovlib/system/public/img/background_2.png b/kodorvan/surikov/system/public/img/background_2.png old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/public/img/background_2.png rename to kodorvan/surikov/system/public/img/background_2.png diff --git a/mirzaev/surikovlib/system/public/img/surikovlib_logo_1.svg b/kodorvan/surikov/system/public/img/surikovlib_logo_1.svg old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/public/img/surikovlib_logo_1.svg rename to kodorvan/surikov/system/public/img/surikovlib_logo_1.svg diff --git a/mirzaev/surikovlib/system/public/img/surikovlib_logo_1_white.svg b/kodorvan/surikov/system/public/img/surikovlib_logo_1_white.svg old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/public/img/surikovlib_logo_1_white.svg rename to kodorvan/surikov/system/public/img/surikovlib_logo_1_white.svg diff --git a/mirzaev/surikovlib/system/public/img/ФЖ-28.png b/kodorvan/surikov/system/public/img/ФЖ-28.png old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/public/img/ФЖ-28.png rename to kodorvan/surikov/system/public/img/ФЖ-28.png diff --git a/mirzaev/surikovlib/system/public/img/школьникам.png b/kodorvan/surikov/system/public/img/школьникам.png old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/public/img/школьникам.png rename to kodorvan/surikov/system/public/img/школьникам.png diff --git a/kodorvan/surikov/system/public/index.php b/kodorvan/surikov/system/public/index.php new file mode 100755 index 0000000..611d6dd --- /dev/null +++ b/kodorvan/surikov/system/public/index.php @@ -0,0 +1,58 @@ +router + ->write('/', new route('index', 'index'), 'GET') + ->write('/account/registration', new route('accounts', 'registration'), 'POST') + ->write('/account/authentication', new route('accounts', 'authentication'), 'POST') + ->write('/account/deauthentication', new route('accounts', 'deauthentication'), 'POST') + ->write('/account/deauthentication', new route('accounts', 'deauthentication'), 'GET') + ->write('/books', new route('books', 'index'), 'GET') + ->write('/books/$id', new route('books', 'index'), 'GET') + ->write('/books/$id/$page', new route('books', 'index'), 'GET') + ->write('/books/$id/$page/rotate', new route('books', 'rotate'), 'POST') + ->write('/books/$id/delete', new route('books', 'delete'), 'POST') + ->write('/storage/books/$id/$file', new route('books', 'read'), 'GET') + ->write('/storage/books/write', new route('books', 'write'), 'POST') + ->write('/kemenov', new route('kemenov', 'index'), 'GET') + ->write('/surikov', new route('surikov', 'index'), 'GET') + ->write('/contacts', new route('contacts', 'index'), 'GET'); + +// Handling the request +$core->start(); diff --git a/mirzaev/surikovlib/system/public/js/auth.js b/kodorvan/surikov/system/public/js/auth.js old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/public/js/auth.js rename to kodorvan/surikov/system/public/js/auth.js diff --git a/mirzaev/surikovlib/system/public/js/delete.js b/kodorvan/surikov/system/public/js/delete.js old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/public/js/delete.js rename to kodorvan/surikov/system/public/js/delete.js diff --git a/mirzaev/surikovlib/system/public/js/rotate.js b/kodorvan/surikov/system/public/js/rotate.js old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/public/js/rotate.js rename to kodorvan/surikov/system/public/js/rotate.js diff --git a/kodorvan/surikov/system/settings/.gitignore b/kodorvan/surikov/system/settings/.gitignore new file mode 100644 index 0000000..ac76a3f --- /dev/null +++ b/kodorvan/surikov/system/settings/.gitignore @@ -0,0 +1 @@ +*.sample.php diff --git a/kodorvan/surikov/system/settings/database.php b/kodorvan/surikov/system/settings/database.php new file mode 100644 index 0000000..467592e --- /dev/null +++ b/kodorvan/surikov/system/settings/database.php @@ -0,0 +1,10 @@ + 'mysql', + 'host' => '127.0.0.1', + 'name' => 'casino_2', + 'login' => 'brawl_stars', + 'password' => 'knopka_bablo_228' +]; diff --git a/mirzaev/surikovlib/system/storage/.gitignore b/kodorvan/surikov/system/storage/.gitignore old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/storage/.gitignore rename to kodorvan/surikov/system/storage/.gitignore diff --git a/mirzaev/surikovlib/system/storage/books/.gitkeep b/kodorvan/surikov/system/storage/books/.gitkeep old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/storage/books/.gitkeep rename to kodorvan/surikov/system/storage/books/.gitkeep diff --git a/mirzaev/surikovlib/system/storage/temp/.gitkeep b/kodorvan/surikov/system/storage/temp/.gitkeep old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/storage/temp/.gitkeep rename to kodorvan/surikov/system/storage/temp/.gitkeep diff --git a/kodorvan/surikov/system/views/templater.php b/kodorvan/surikov/system/views/templater.php new file mode 100755 index 0000000..dbb58db --- /dev/null +++ b/kodorvan/surikov/system/views/templater.php @@ -0,0 +1,213 @@ + + */ +final class templater extends controller implements array_access +{ + /** + * Twig + * + * @var twig $twig Instance of the twig templater + */ + readonly public twig $twig; + + /** + * Variables + * + * @var array $variables Registry of view global variables + */ + public array $variables = []; + + /** + * Constructor of an instance + * + * @return void + */ + public function __construct(?account $account = null) + { + // Initializing the Twig instance + $this->twig = new twig(new FilesystemLoader(VIEWS)); + + // Initializing global variables + $this->twig->addGlobal('theme', 'default'); + $this->twig->addGlobal('server', $_SERVER); + $this->twig->addGlobal('cookies', $_COOKIE); + if ($account instanceof account) $this->twig->addGlobal('account', $account); + $this->twig->addGlobal('language', $language = $session?->buffer['language'] ?? language::en); + } + + /** + * Render + * + * Render the HTML-document + * + * @param string $file Related path to a HTML-document + * @param array $variables Registry of variables to push into registry of global variables + * + * @return ?string HTML-document + */ + public function render(string $file, array $variables = []): ?string + { + // Generation and exit (success) + return $this->twig->render('themes' . DIRECTORY_SEPARATOR . $this->twig->getGlobals()['theme'] . DIRECTORY_SEPARATOR . $file, $variables + $this->variables); + + } + + /** + * Write + * + * Write the variable into the registry of the view global variables + * + * @param string $name Name of the variable + * @param mixed $value Value of the variable + * + * @return void + */ + public function __set(string $name, mixed $value = null): void + { + // Write the variable and exit (success) + $this->variables[$name] = $value; + } + + /** + * Read + * + * Read the variable from the registry of the view global variables + * + * @param string $name Name of the variable + * + * @return mixed Content of the variable, if they are found + */ + public function __get(string $name): mixed + { + // Read the variable and exit (success) + return $this->variables[$name]; + } + + /** + * Delete + * + * Delete the variable from the registry of the view global variables + * + * @param string $name Name of the variable + * + * @return void + */ + public function __unset(string $name): void + { + // Delete the variable and exit (success) + unset($this->variables[$name]); + } + + /** + * Check of initialization + * + * Check of initialization in the registry of the view global variables + * + * @param string $name Name of the variable + * + * @return bool The variable is initialized? + */ + public function __isset(string $name): bool + { + // Check of initialization of the variable and exit (success) + return isset($this->variables[$name]); + } + + /** + * Write + * + * Write the variable into the registry of the view global variables + * + * @param mixed $name Name of an offset of the variable + * @param mixed $value Value of the variable + * + * @return void + */ + public function offsetSet(mixed $name, mixed $value): void + { + // Write the variable and exit (success) + $this->variables[$name] = $value; + } + + /** + * Read + * + * Read the variable from the registry of the view global variables + * + * @param mixed $name Name of the variable + * + * @return mixed Content of the variable, if they are found + */ + public function offsetGet(mixed $name): mixed + { + // Read the variable and exit (success) + return $this->variables[$name]; + } + + /** + * Delete + * + * Delete the variable from the registry of the view global variables + * + * @param mixed $name Name of the variable + * + * @return void + */ + public function offsetUnset(mixed $name): void + { + // Delete the variable and exit (success) + unset($this->variables[$name]); + } + + /** + * Check of initialization + * + * Check of initialization in the registry of the view global variables + * + * @param mixed $name Name of the variable + * + * @return bool The variable is initialized? + */ + public function offsetExists(mixed $name): bool + { + // Check of initialization of the variable and exit (success) + return isset($this->variables[$name]); + } +} diff --git a/mirzaev/surikovlib/system/views/auth.html b/kodorvan/surikov/system/views/themes/default/authentication.html old mode 100644 new mode 100755 similarity index 100% rename from mirzaev/surikovlib/system/views/auth.html rename to kodorvan/surikov/system/views/themes/default/authentication.html diff --git a/mirzaev/surikovlib/system/views/books/book.html b/kodorvan/surikov/system/views/themes/default/books/book.html old mode 100644 new mode 100755 similarity index 97% rename from mirzaev/surikovlib/system/views/books/book.html rename to kodorvan/surikov/system/views/themes/default/books/book.html index e8e50b1..fa861f1 --- a/mirzaev/surikovlib/system/views/books/book.html +++ b/kodorvan/surikov/system/views/themes/default/books/book.html @@ -1,4 +1,4 @@ -{% extends "core.html" %} +{% extends "/themes/default/core.html" %} {% block main %} diff --git a/mirzaev/surikovlib/system/views/books/index.html b/kodorvan/surikov/system/views/themes/default/books/index.html old mode 100644 new mode 100755 similarity index 96% rename from mirzaev/surikovlib/system/views/books/index.html rename to kodorvan/surikov/system/views/themes/default/books/index.html index c6afb3e..03c7c23 --- a/mirzaev/surikovlib/system/views/books/index.html +++ b/kodorvan/surikov/system/views/themes/default/books/index.html @@ -1,4 +1,4 @@ -{% extends "core.html" %} +{% extends "/themes/default/core.html" %} {% block main %} diff --git a/mirzaev/surikovlib/system/views/core.html b/kodorvan/surikov/system/views/themes/default/core.html old mode 100644 new mode 100755 similarity index 53% rename from mirzaev/surikovlib/system/views/core.html rename to kodorvan/surikov/system/views/themes/default/core.html index bc1c0ba..4692020 --- a/mirzaev/surikovlib/system/views/core.html +++ b/kodorvan/surikov/system/views/themes/default/core.html @@ -3,7 +3,7 @@
- {% include 'head.html' %} + {% include '/themes/default/head.html' %} @@ -15,20 +15,20 @@ - {% include 'header.html' %} + {% include '/themes/default/header.html' %}