refresh after 3-4 years

This commit is contained in:
2025-08-22 10:02:21 +03:00
parent c06e201c2b
commit 7fe34899a9
66 changed files with 2686 additions and 1951 deletions

0
.gitignore vendored Normal file → Executable file
View File

0
LICENSE Normal file → Executable file
View File

0
README.md Normal file → Executable file
View File

17
composer.json Normal file → Executable file
View File

@@ -1,9 +1,9 @@
{ {
"name": "mirzaev/surikovlib", "name": "kodorvan/surikov",
"description": "Онлайн библеотека музея имени Сурикова", "description": "Online library of the Surikov Museum",
"type": "project", "type": "project",
"license": "AGPL-3.0-or-later", "license": "WTFPL",
"homepage": "https://git.hood.su/mirzaev/surikovlib", "homepage": "https://git.svoboda.works/kodorvan/surikov",
"authors": [ "authors": [
{ {
"name": "Arsen Mirzaev Tatyano-Muradovich", "name": "Arsen Mirzaev Tatyano-Muradovich",
@@ -13,13 +13,14 @@
} }
], ],
"require": { "require": {
"php": "^8.0.0", "php": "^8.4.0",
"mirzaev/minimal": "^2.0.x-dev", "mirzaev/minimal": "^3.6.2",
"twig/twig": "^3.3" "twig/twig": "^3.3",
"mirzaev/languages": "^1.0"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"mirzaev\\surikovlib\\": "mirzaev/surikovlib/system" "kodorvan\\surikov\\": "kodorvan/surikov/system"
} }
} }
} }

230
composer.lock generated Normal file → Executable file
View File

@@ -4,21 +4,59 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "1c826a114d11e8301a0f17171d987459", "content-hash": "97cd0861246168b4da62751f2317bc62",
"packages": [ "packages": [
{ {
"name": "mirzaev/minimal", "name": "mirzaev/languages",
"version": "2.0.x-dev", "version": "1.0.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://git.hood.su/mirzaev/minimal", "url": "https://git.svoboda.works/mirzaev/languages",
"reference": "7777d7af1733d661a36551a0fdcf27a972e4ef81" "reference": "eceff49204c718243f24e3da42294c5ea5b29e01"
}, },
"require": { "require": {
"php": "~8.0" "php": "^8.4"
}, },
"suggest": { "type": "library",
"ext-PDO": "Для работы с базами данных на SQL (MySQL, PostreSQL...)" "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", "type": "framework",
"autoload": { "autoload": {
@@ -35,37 +73,105 @@
"name": "Arsen Mirzaev Tatyano-Muradovich", "name": "Arsen Mirzaev Tatyano-Muradovich",
"email": "arsen@mirzaev.sexy", "email": "arsen@mirzaev.sexy",
"homepage": "https://mirzaev.sexy", "homepage": "https://mirzaev.sexy",
"role": "Developer" "role": "Programmer"
} }
], ],
"description": "Легковесный MVC фреймворк который следует твоим правилам, а не диктует свои", "description": "My vision of a good framework",
"homepage": "https://git.hood.su/mirzaev/minimal", "homepage": "https://git.mirzaev.sexy/mirzaev/minimal",
"keywords": [ "keywords": [
"framework", "framework",
"lightweight",
"mvc" "mvc"
], ],
"support": { "support": {
"docs": "https://git.hood.su/mirzaev/minimal/manual", "docs": "https://git.mirzaev.sexy/mirzaev/minimal/wiki",
"issues": "https://git.hood.su/mirzaev/minimal/issues" "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", "name": "symfony/deprecation-contracts",
"version": "v1.25.0", "version": "v3.6.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git", "url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "30885182c981ab175d4d034db0f6f469898070ab" "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
"reference": "30885182c981ab175d4d034db0f6f469898070ab", "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
"shasum": "" "shasum": ""
}, },
"require": { "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": { "provide": {
"ext-ctype": "*" "ext-ctype": "*"
@@ -75,12 +181,9 @@
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": {
"dev-main": "1.23-dev"
},
"thanks": { "thanks": {
"name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill",
"url": "https://github.com/symfony/polyfill" "name": "symfony/polyfill"
} }
}, },
"autoload": { "autoload": {
@@ -114,7 +217,7 @@
"portable" "portable"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
}, },
"funding": [ "funding": [
{ {
@@ -125,29 +228,34 @@
"url": "https://github.com/fabpot", "url": "https://github.com/fabpot",
"type": "github" "type": "github"
}, },
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{ {
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-10-20T20:35:02+00:00" "time": "2024-09-09T11:45:10+00:00"
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
"version": "v1.25.0", "version": "v1.33.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git", "url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.1" "ext-iconv": "*",
"php": ">=7.2"
}, },
"provide": { "provide": {
"ext-mbstring": "*" "ext-mbstring": "*"
@@ -157,12 +265,9 @@
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": {
"dev-main": "1.23-dev"
},
"thanks": { "thanks": {
"name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill",
"url": "https://github.com/symfony/polyfill" "name": "symfony/polyfill"
} }
}, },
"autoload": { "autoload": {
@@ -197,7 +302,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
}, },
"funding": [ "funding": [
{ {
@@ -208,43 +313,50 @@
"url": "https://github.com/fabpot", "url": "https://github.com/fabpot",
"type": "github" "type": "github"
}, },
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{ {
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-11-30T18:21:41+00:00" "time": "2024-12-23T08:48:59+00:00"
}, },
{ {
"name": "twig/twig", "name": "twig/twig",
"version": "v3.3.8", "version": "v3.21.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/twigphp/Twig.git", "url": "https://github.com/twigphp/Twig.git",
"reference": "972d8604a92b7054828b539f2febb0211dd5945c" "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/972d8604a92b7054828b539f2febb0211dd5945c", "url": "https://api.github.com/repos/twigphp/Twig/zipball/285123877d4dd97dd7c11842ac5fb7e86e60d81d",
"reference": "972d8604a92b7054828b539f2febb0211dd5945c", "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.2.5", "php": ">=8.1.0",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-ctype": "^1.8", "symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3" "symfony/polyfill-mbstring": "^1.3"
}, },
"require-dev": { "require-dev": {
"psr/container": "^1.0", "phpstan/phpstan": "^2.0",
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" "psr/container": "^1.0|^2.0",
"symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
}, },
"type": "library", "type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.3-dev"
}
},
"autoload": { "autoload": {
"files": [
"src/Resources/core.php",
"src/Resources/debug.php",
"src/Resources/escaper.php",
"src/Resources/string_loader.php"
],
"psr-4": { "psr-4": {
"Twig\\": "src/" "Twig\\": "src/"
} }
@@ -277,7 +389,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/twigphp/Twig/issues", "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": [ "funding": [
{ {
@@ -289,20 +401,18 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-02-04T06:59:48+00:00" "time": "2025-05-03T07:21:55+00:00"
} }
], ],
"packages-dev": [], "packages-dev": [],
"aliases": [], "aliases": [],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": { "stability-flags": {},
"mirzaev/minimal": 20
},
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
"php": "^8.0.0" "php": "^8.4.0"
}, },
"platform-dev": [], "platform-dev": {},
"plugin-api-version": "2.2.0" "plugin-api-version": "2.6.0"
} }

View File

@@ -0,0 +1,283 @@
<?php
declare(strict_types=1);
namespace kodorvan\surikov\controllers;
// The project
use kodorvan\surikov\controllers\core,
kodorvan\surikov\models\account;
// Framework for PHP
use mirzaev\minimal\http\enumerations\content,
mirzaev\minimal\http\enumerations\protocol,
mirzaev\minimal\http\enumerations\status;
/**
* Accounts controller
*
* @package kodorvan\surikov\controllers
*
* @param array $errors Registry of errors
*
* @method null index() Account page
* @method string|null registration() Registration
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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;
}
}

View File

@@ -0,0 +1,313 @@
<?php
declare(strict_types=1);
namespace kodorvan\surikov\controllers;
// The project
use kodorvan\surikov\controllers\core,
kodorvan\surikov\models\account,
kodorvan\surikov\models\book;
// Framework for PHP
use mirzaev\minimal\http\enumerations\content,
mirzaev\minimal\http\enumerations\status;
/**
* Books controller
*
* @package kodorvan\surikov\controllers
*
* @param array $errors Registry of errors
*
* @method null index() Books page
* @method null write() Write
* @method null read(int|string|null $identifier, int|string|null $page) Read
* @method null delete(int|string|null $identifier) Delete
* @method null rotate(int|string|null $identifier, int|string|null $page) Rotate
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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;
}
}

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace kodorvan\surikov\controllers;
// Files of the project
use kodorvan\surikov\controllers\core,
kodorvan\surikov\models\account;
// Framework for PHP
use mirzaev\minimal\http\enumerations\content,
mirzaev\minimal\http\enumerations\status;
/**
* Contacts controller
*
* @package kodorvan\surikov\controllers
*
* @param array $errors Registry of errors
*
* @method null index() Contacts page
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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;
}
}

View File

@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace kodorvan\surikov\controllers;
// Files of the project
use kodorvan\surikov\views\templater,
kodorvan\surikov\models\core as models,
kodorvan\surikov\models\account;
// Framework for PHP
use mirzaev\minimal\core as minimal,
mirzaev\minimal\controller,
mirzaev\minimal\http\response,
mirzaev\minimal\http\enumerations\status;
// Library for languages support
use mirzaev\languages\language;
/**
* Core controller
*
* @package kodorvan\surikov\controllers
*
* @param language $language Language
* @param response $response Response
* @param array $errors Registry of errors
*
* @method void __construct(minimal $minimal) Constructor
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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);
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace kodorvan\surikov\controllers;
// Files of the project
use kodorvan\surikov\controllers\core;
// Framework for PHP
use mirzaev\minimal\http\enumerations\content,
mirzaev\minimal\http\enumerations\status;
/**
* Errors controller
*
* @package kodorvan\surikov\controllers
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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 (на стороне сервера)';
}
}

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace kodorvan\surikov\controllers;
// Files of the project
use kodorvan\surikov\controllers\core,
kodorvan\surikov\models\account;
// Framework for PHP
use mirzaev\minimal\http\enumerations\content,
mirzaev\minimal\http\enumerations\status;
/**
* Index controller
*
* @package kodorvan\surikov\controllers
*
* @param array $errors Registry of errors
*
* @method null index() Main page
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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;
}
}

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace kodorvan\surikov\controllers;
// The project
use kodorvan\surikov\controllers\core,
kodorvan\surikov\models\account;
// Framework for PHP
use mirzaev\minimal\http\enumerations\content,
mirzaev\minimal\http\enumerations\status;
use Twig\Environment as view;
/**
* Kemenov constroller
*
* @package kodorvan\surikov\controllers
*
* @param array $errors Registry of errors
*
* @method null index() Kemenov page
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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;
}
}

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace kodorvan\surikov\controllers;
// The project
use kodorvan\surikov\controllers\core,
kodorvan\surikov\models\account;
// Framework for PHP
use mirzaev\minimal\http\enumerations\content,
mirzaev\minimal\http\enumerations\status;
/**
* Surikov controller
*
* @package kodorvan\surikov\controllers
*
* @param array $errors Registry of errors
*
* @method null index() Surikov page
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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;
}
}

View File

@@ -0,0 +1,711 @@
<?php
declare(strict_types=1);
namespace kodorvan\surikov\models;
// Files of the project
use kodorvan\surikov\models\core;
// Built-in libraries
use pdo,
exception;
/**
* Account
*
* @package kodorvan\surikov\models
*
* @param int $identifier Identifier
* @param string $password Password
* @param string|null $hash Hash
* @param int $time Time
* @param array $permissions Permissions
*
* @method void __construct(array $parameters) Constructor
* @method static static|null registrate(string $mail, string $password, bool $authenticate, array &$errors) Registrate
* @method static static|null authenticate(string $mail, string $password, bool $remember, array &$errors) Authenticate
* @method static bool deauthenticate(array &$errors) Deauthenticate
* @method static static|null initialize(?int $identifier, array &$errors) Initialize
* @method static array permissions(int $identifier, array &$errors) Permissions
* @method static bool|null access(string $permission, array &$errors) Access
* @method static static|null write(string $mail, string $password, array &$errors) Write
* @method static static|null read(array $expressions, array &$errors) Read
* @method static array|bool hash(int $identifier, string|null $hash, int|null $time, array &$errors) Hash
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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;
}
}

View File

@@ -0,0 +1,412 @@
<?php
declare(strict_types=1);
namespace kodorvan\surikov\models;
// Files of the project
use kodorvan\surikov\models\account;
// Built-in libraries
use pdo,
exception;
/**
* Book model
*
* @package kodorvan\surikov\models
*
* @param
*
* @method static int|null write(string $title, ?string $description, ?int $account, array &$errors) Write
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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;
}
}

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace kodorvan\surikov\models;
// Framework for PHP
use mirzaev\minimal\model;
// Built-in libraries
use pdo,
pdoexception,
exception;
/**
* Core model
*
* @package kodorvan\surikov\models
*
* @param pdo $database Database
*
* @method void __construct(?pdo $database) Constructor
* @method void __set(string $name, mixed $value) Write
* @method mixed __get(string $name) Read
* @method bool __isset(string $name) Isset
* @method void __unset(string $name) Deinitialize
* @method mixed __callStatic(string $name, mixed $arguments) Call static
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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']);
}
}

View File

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 172 KiB

View File

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

View File

Before

Width:  |  Height:  |  Size: 853 KiB

After

Width:  |  Height:  |  Size: 853 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 127 KiB

View File

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace kodorvan\surikov;
// Framework for PHP
use mirzaev\minimal\core,
mirzaev\minimal\route;
// Initializing path to the public directory
define('INDEX', __DIR__);
// Initializing path to the project root directory
define('ROOT', INDEX . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR);
// Initializing path to the directory of views
define('VIEWS', INDEX . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'views');
// Initializing path to the directory of the storage
define('STORAGE', INDEX . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'storage');
// Initializing path to the directory of settings
define('SETTINGS', INDEX . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'settings');
// Initializing the database connection parameters
define('DATABASE', require(SETTINGS . DIRECTORY_SEPARATOR . 'database.php'));
ini_set('error_reporting', E_ALL);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
// Initializing dependencies
require ROOT . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
// Initializing the core
$core = new core(namespace: __NAMESPACE__);
// Initializing routes
$core->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();

View File

@@ -0,0 +1 @@
*.sample.php

View File

@@ -0,0 +1,10 @@
<?php
// Exit (success)
return [
'type' => 'mysql',
'host' => '127.0.0.1',
'name' => 'casino_2',
'login' => 'brawl_stars',
'password' => 'knopka_bablo_228'
];

View File

@@ -0,0 +1,213 @@
<?php
declare(strict_types=1);
namespace kodorvan\surikov\views;
// Files of the project
use kodorvan\surikov\models\account;
// Framework for PHP
use mirzaev\minimal\controller;
// Library for languages support
use mirzaev\languages\language;
// Templater of views
use Twig\Loader\FilesystemLoader,
Twig\Environment as twig,
Twig\Extra\Intl\IntlExtension as intl,
Twig\TwigFilter,
Twig\TwigFunction;
// Built-in libraries
use ArrayAccess as array_access,
Error as error;
/**
* Templater
*
* @package kodorvan\surikov\views
*
* @param twig $twig Instance of the twig templater
* @param array $variables Registry of view global variables
*
* @method void __construct() Constructor
* @method string|null render(string $file, ?array $variables) Render the HTML-document
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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]);
}
}

View File

@@ -1,4 +1,4 @@
{% extends "core.html" %} {% extends "/themes/default/core.html" %}
{% block main %} {% block main %}
<link href="/css/books.css" rel="stylesheet"> <link href="/css/books.css" rel="stylesheet">

View File

@@ -1,4 +1,4 @@
{% extends "core.html" %} {% extends "/themes/default/core.html" %}
{% block main %} {% block main %}
<link href="/css/books.css" rel="stylesheet"> <link href="/css/books.css" rel="stylesheet">

View File

@@ -3,7 +3,7 @@
<html lang="ru"> <html lang="ru">
<head> <head>
{% include 'head.html' %} {% include '/themes/default/head.html' %}
<link href="/css/main.css" rel="stylesheet"> <link href="/css/main.css" rel="stylesheet">
@@ -15,20 +15,20 @@
</head> </head>
<body> <body>
{% include 'header.html' %} {% include '/themes/default/header.html' %}
<aside> <aside>
{% include 'sidebar.html' %} {% include '/themes/default/sidebar.html' %}
</aside><!----><main> </aside><!----><main>
{% block main %} {% block main %}
{% include 'calculators/index.html' %} {% include '/themes/default/calculators/index.html' %}
{% endblock %} {% endblock %}
</main> </main>
{% include 'footer.html' %} {% include '/themes/default/footer.html' %}
</body> </body>
{% include 'js.html' %} {% include '/themes/default/js.html' %}
</body> </body>
</html> </html>

View File

@@ -1,4 +1,4 @@
{% extends "core.html" %} {% extends "/themes/default/core.html" %}
{% block main %} {% block main %}
<img class="banner unselectable" src="/img/школьникам.png" alt="Предложение для школьников"> <img class="banner unselectable" src="/img/школьникам.png" alt="Предложение для школьников">

View File

@@ -1,4 +1,4 @@
{% extends "core.html" %} {% extends "/themes/default/core.html" %}
{% block main %} {% block main %}
<link href="/css/pages.css" rel="stylesheet"> <link href="/css/pages.css" rel="stylesheet">

View File

@@ -1,4 +1,4 @@
{% extends "core.html" %} {% extends "/themes/default/core.html" %}
{% block main %} {% block main %}
<link href="/css/pages.css" rel="stylesheet"> <link href="/css/pages.css" rel="stylesheet">

View File

@@ -1,4 +1,4 @@
{% extends "core.html" %} {% extends "/themes/default/core.html" %}
{% block main %} {% block main %}
<link href="/css/pages.css" rel="stylesheet"> <link href="/css/pages.css" rel="stylesheet">

View File

@@ -1,6 +1,6 @@
<link href="/css/banners.css" rel="stylesheet"> <link href="/css/banners.css" rel="stylesheet">
{% include 'auth.html' %} {% include '/themes/default/authentication.html' %}
{% include 'vk.html' %} {% include '/themes/default/vk.html' %}
<!-- <section class="banners"> <!-- <section class="banners">
</section> --> </section> -->

View File

@@ -1,168 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\surikovlib\controllers;
use mirzaev\surikovlib\controllers\core;
use mirzaev\surikovlib\models\accounts_model as accounts;
/**
* Контроллер пользователей
*
* @package mirzaev\surikovlib\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class accounts_controller extends core
{
/**
* Страница профиля
*
* @param array $vars
*/
public function index(array $vars = []): ?string
{
return null;
}
/**
* Регистрация
*
* @param array $vars Параметры запроса
*
* @return string|null HTML-документ
*/
public function registration(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = [];
if ($vars['account'] = accounts::registration(mail: $vars['mail'] ?? null, password: $vars['password'] ?? null, errors: $vars['errors'])) {
// Удалось зарегистрироваться
if ($vars['account'] = accounts::authentication($vars['mail'] ?? null, $vars['password'] ?? null, (bool) ($vars['remember'] ?? false), $vars)) {
// Удалось аутентифицироваться
} else {
// Не удалось аутентифицироваться
// Запись кода ответа
http_response_code(401);
}
} else {
// Не удалось зарегистрироваться
// Запись кода ответа
http_response_code(401);
}
// Инициализаци пути для перенаправления
$redirect = isset($vars['redirect']) ? $vars['redirect'] : $_SERVER['HTTP_REFERER'] ?? '/';
// Перенаправление
header("Location: $redirect", response_code: 303);
return null;
}
/**
* Аутентификация
*
* @param array $vars Параметры запроса
*
* @return string|null HTML-документ
*/
public function authentication(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = [];
if ($vars['account'] = accounts::authentication($vars['mail'] ?? null, $vars['password'] ?? null, (bool) ($vars['remember'] ?? false), errors: $vars['errors'])) {
// Удалось аутентифицироваться
} else {
// Не удалось аутентифицироваться
// Запись кода ответа
http_response_code(401);
}
// Инициализаци пути для перенаправления
$redirect = isset($vars['redirect']) ? $vars['redirect'] : $_SERVER['HTTP_REFERER'] ?? '/';
// Перенаправление
header("Location: $redirect", response_code: 303);
return null;
}
/**
* Деаутентификация
*
* @param array $vars Параметры запроса
*
* @return string|null HTML-документ
*/
public function deauthentication(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = [];
if (accounts::deauthentication(errors: $vars['errors'])) {
// Удалось деаутентифицироваться
// Деинициализация аккаунта
$vars['account'] = null;
} else {
// Не удалось деаутентифицироваться
// Запись кода ответа
http_response_code(500);
}
// Перенаправление
header('Location: /', response_code: 303);
return null;
}
/**
* Данные аккаунта
*
* Если информацию запрашивает администратор, то вернётся вся, иначе только разрешённая публично
*
* @param array $vars Параметры запроса
*
* @return string JSON-документ
*/
public function data(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = [];
if ($account = accounts::read(['id' => $vars['id']], $vars['errors'])) {
// Найдены данные запрашиваемого аккаунта
// Инициализация аккаунта
$vars['account'] = accounts::init(errors: $vars['errors']);
if ($vars['account'] && $vars['account']->permissions['accounts'] ?? 0 === 1) {
// Удалось аутентифицироваться и пройдена проверка авторизации
} else {
// Не удалось аутентифицироваться
// Удаление запрещённых к публикации полей
$account->password = $account->hash = $account->time = null;
}
// Генерация ответа
return json_encode($account ?? '');
} else {
// Не найдены данные запрашиваемого аккаунта
// Запись кода ответа
http_response_code(404);
}
return null;
}
}

View File

@@ -1,191 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\surikovlib\controllers;
use mirzaev\surikovlib\controllers\core;
use mirzaev\surikovlib\models\accounts_model as accounts;
use mirzaev\surikovlib\models\books_model as books;
/**
* Контроллер пользователей
*
* @package mirzaev\surikovlib\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class books_controller extends core
{
/**
* Страница с книгами (или книгой)
*
* @param array $vars
*/
public function index(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = [];
// Проверка аутентифицированности
$vars['account'] = accounts::init(errors: $vars['errors']);
if (isset($vars['id'])) {
// Определённая книга
// Чтение метаданных книги
$vars['book'] = books::read(['id' => $vars['id']])[0] ?? null;
// Инициализация страницы
if (empty($vars['page']) || $vars['page'] < 0) $vars['page'] = 0;
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . 'book.html', $vars);
} else {
// Все книги
// Чтение книг
$vars['books'] = books::read(limit: 30, page: isset($vars['page']) && $vars['page'] > 0 ? $vars['page'] : 1);
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . 'index.html', $vars);
}
}
/**
* Запись
*
* @param array $vars Параметры
* @param array $files Файлы
*
* @return string|null HTML-документ
*/
public function write(array $vars = [], array $files = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = [];
if (accounts::init(errors: $vars['errors'])->access('books')) {
// Найден и авторизован аккаунт
if (count($books = books::import($files['books'] ?? [], errors: $vars['errors'])) > 0) {
// Загружены книги
} else {
// Не загружены книги
}
}
// Перенаправление
header('Location: /books', response_code: 303);
return 'wtf';
}
/**
* Чтение
*
* @param array $vars
*
* @return string|null Файл, если найден
*/
public function read(array $vars = []): ?string
{
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 null;
}
/**
* Удаление
*
* @param array $vars Параметры запроса
*
* @return string|null HTML-документ
*/
public function delete(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = [];
if (accounts::init(errors: $vars['errors'])->access('books')) {
// Найден и авторизован аккаунт
if (isset($vars['id'])) {
// Найдены обязательные входные параметры
if (books::delete((int) $vars['id'], $vars['errors'])) {
// Удалена книга из базы данных
// Инициализация пути до книги
$book = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $vars['id'];
if (file_exists($book)) {
// Найдена книга
// Удаление книги
exec('rm -rf ' . escapeshellarg($book));
// Запись статуса выполнения в буфер вывода
$status = true;
}
}
}
}
return json_encode([
'status' => $status ?? false,
'errors' => $vars['errors']
]);
}
/**
* Поворот
*
* @param array $vars
*
* @return string|null JSON
*/
public function rotate(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = [];
if (accounts::init(errors: $vars['errors'])->access('books')) {
// Найден и авторизован аккаунт
if (isset($vars['id'], $vars['page'])) {
// Найдены обязательные входные параметры
// Поворот страницы
$status = books::rotate((int) $vars['id'], (int) $vars['page'], $vars['errors']);
}
}
return json_encode([
'status' => $status ?? false,
'errors' => $vars['errors']
]);
}
}

View File

@@ -1,28 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\surikovlib\controllers;
use mirzaev\surikovlib\controllers\core;
use mirzaev\surikovlib\models\accounts_model as accounts;
use Twig\Environment as view;
/**
* Контроллер страницы "контакты"
*
* @package mirzaev\surikovlib\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class contacts_controller extends core
{
public function index(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = [];
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'contacts' . DIRECTORY_SEPARATOR . 'index.html', $vars);
}
}

View File

@@ -1,34 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\surikovlib\controllers;
use mirzaev\surikovlib\views\manager;
use mirzaev\surikovlib\models\core as models;
use mirzaev\minimal\controller;
/**
* Ядро контроллеров
*
* @package mirzaev\surikovlib\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
class core extends controller
{
/**
* Конструктор
*
* @return void
*/
public function __construct()
{
parent::__construct();
// Инициализация ядра моделей (соединение с базой данных...)
new models();
$this->view = new manager;
}
}

View File

@@ -1,31 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\surikovlib\controllers;
use mirzaev\surikovlib\controllers\core;
use Twig\Loader\FilesystemLoader;
use Twig\Environment as view;
/**
* Контроллер основной страницы
*
* @package mirzaev\surikovlib\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class errors_controller extends core
{
public function error404(): ?string
{
// Генерация представления
return 'Ошибка 404 (не найдено)';
}
public function error500(): ?string
{
// Генерация представления
return 'Ошибка 500 (на стороне сервера)';
}
}

View File

@@ -1,28 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\surikovlib\controllers;
use mirzaev\surikovlib\controllers\core;
use mirzaev\surikovlib\models\accounts_model as accounts;
use Twig\Environment as view;
/**
* Контроллер страницы "кеменов"
*
* @package mirzaev\surikovlib\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class kemenov_controller extends core
{
public function index(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = [];
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'kemenov' . DIRECTORY_SEPARATOR . 'index.html', $vars);
}
}

View File

@@ -1,32 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\surikovlib\controllers;
use mirzaev\surikovlib\controllers\core;
use mirzaev\surikovlib\models\accounts_model as accounts;
use Twig\Loader\FilesystemLoader;
use Twig\Environment as view;
/**
* Контроллер основной страницы
*
* @package mirzaev\surikovlib\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class main_controller extends core
{
public function index(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = [];
// Проверка аутентифицированности
$vars['account'] = accounts::init(errors: $vars['errors']);
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'main' . DIRECTORY_SEPARATOR . 'index.html', $vars);
}
}

View File

@@ -1,28 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\surikovlib\controllers;
use mirzaev\surikovlib\controllers\core;
use mirzaev\surikovlib\models\accounts_model as accounts;
use Twig\Environment as view;
/**
* Контроллер страницы "суриков"
*
* @package mirzaev\surikovlib\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class surikov_controller extends core
{
public function index(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = [];
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'surikov' . DIRECTORY_SEPARATOR . 'index.html', $vars);
}
}

View File

@@ -1,638 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\surikovlib\models;
use pdo;
use exception;
/**
* Модель регистрации, аутентификации и авторизации
*
* @package mirzaev\surikovlib\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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 $mail Почта
* @param string $password Пароль
* @param bool $authenticate Автоматическая аутентификация в случае успешной регистрации
* @param array &$errors Журнал ошибок
*
* @return static|null Аккаунт
*/
public static function registration(string $mail, string $password, bool $authenticate = true, array &$errors = []): ?static
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
if (static::init(errors: $errors)) {
// Аутентифицирован пользователь
// Запись ошибки
throw new exception('Уже аутентифицирован');
}
if (empty($account = static::read(['mail' => $mail]))) {
// Не удалось найти аккаунт
if (static::write($mail, $password, $errors)) {
// Удалось зарегистрироваться
if ($authenticate) {
// Запрошена аутентификация
// Аутентификация
$account = static::authentication($mail, $password, true, $errors);
}
return $account;
}
} else {
// Удалось найти аккаунт
return $account;
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Аутентификация
*
* @param string $mail Почта
* @param string $password Пароль
* @param bool $remember Функция "Запомнить меня" - увеличенное время хранения cookies
* @param array &$errors Журнал ошибок
*
* @return static|null Аккаунт
*/
public static function authentication(string $mail, string $password, bool $remember = false, array &$errors = []): ?static
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
if (static::init(errors: $errors)) {
// Аутентифицирован пользователь
// Запись ошибки
throw new exception('Уже аутентифицирован');
}
if (empty($account = static::read(['mail' => $mail]))) {
// Не удалось найти аккаунт
throw new exception('Не удалось найти аккаунт');
}
if (password_verify($password, $account->password)) {
// Совпадают хеши паролей
// Инициализация идентификатора сессии
session_id((string) $account->id);
// Инициализация названия сессии
session_name('id');
// Инициализация сессии
session_start();
// Инициализация времени хранения хеша
$time = time() + ($remember ? 604800 : 86400);
// Инициализация хеша
$hash = static::hash((int) $account->id, crypt($account->password, time() . $account->id), $time, $errors)['hash'];
// Инициализация cookies
setcookie('hash', $hash, $time, path: '/', secure: true);
return $account;
} else {
// Не совпадают хеши паролей
throw new exception('Неправильный пароль');
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Аутентификация
*
* @param array &$errors Журнал ошибок
*
* @return bool Удалось ли деаутентифицироваться
*/
public static function deauthentication(array &$errors = []): bool
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
if ($account = static::init(errors: $errors)) {
// Аутентифицирован пользователь
// Инициализация запроса
$request = static::$db->prepare("UPDATE `accounts` SET `hash` = null, `time` = 0 WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $account->id,
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
// Деинициализация cookies
setcookie('id', '', 0, path: '/', secure: true);
setcookie('hash', '', 0, path: '/', secure: true);
return true;
} else {
// Не аутентифицирован пользователь
// Запись ошибки
throw new exception('Не аутентифицирован');
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return false;
}
/**
* Инициализация
*
* @param int|null $account Аккаунт (идентификатор)
* @param array &$errors Журнал ошибок
*
* @return static|null Аккаунт
*/
public static function init(?int $account = null, array &$errors = []): ?static
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
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']) {
// Совпадает переданный хеш с тем, что хранится в базе данных
} else {
// Не совпадает переданный хеш с тем, что хранится в базе данных
// Генерация ошибки
throw new exception('Вы аутентифицированы с другого устройства (не совпадают хеши аутентификации)');
}
if (empty($account = static::read([
'id' => $_COOKIE['id'],
'hash' => $_COOKIE['hash']
]))) {
// Не найден аккаунт или связка аккаунта с хешем
// Генерация ошибки
throw new exception('Не найден пользователь или время аутентификации истекло');
}
} else {
// Не найдены параметры для поиска аккаунта
return null;
}
// Чтение разрешений
$account->permissions = static::permissions((int) $account->id, $errors);
return $account;
} catch (exception $e) {
// Запись в журнал ошибок
$errors['account'][]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Прочитать разрешения из базы данных
*
* @param int $id Идентификатор аккаунта
* @param array &$errors Журнал ошибок
*
* @return array Разрешения аккаунта, если найдены
*/
public static function permissions(int $id, array &$errors = []): array
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `permissions` WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $id
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
if (empty($response = $request->fetch(pdo::FETCH_ASSOC))) {
// Не найдены разрешения
// Генерация ошибки
throw new exception('Не найдены разрешения');
}
// Удаление ненужных данных
unset($response['id']);
return $response;
} catch (exception $e) {
// Запись в журнал ошибок
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return [];
}
/**
* Проверить разрешение
*
* @param string $permission Разрешение
* @param array &$errors Журнал ошибок
*
* @return bool|null Статус разрешения, если оно записано
*/
public function access(string $permission, array &$errors = []): ?bool
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
return isset($this->permissions[$permission]) ? (bool) $this->permissions[$permission] : null;
} catch (exception $e) {
// Запись в журнал ошибок
$errors['account'][]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Запись в базу данных
*
* @param string $mail Почта
* @param string $password Пароль
* @param array &$errors Журнал ошибок
*
* @return static|null Аккаунт
*/
public static function write(string $mail, string $password, array &$errors = []): ?static
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
// Инициализация параметров запроса
$params = [];
try {
// Проверка параметра
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 символов');
// Запись в буфер параметров запроса
$params[':mail'] = $mail;
// Проверка параметра
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);
// Инициализация запроса
$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
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
} catch (exception $e) {
// Запись в журнал ошибок
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
// Конец выполнения
end:
return $account ?? [];
}
/**
* Чтение из базы данных
*
* @param array $expression Выражение поиска ('поле' => 'значение')
* @param array &$errors Журнал ошибок
*
* @return static|null Аккаунт
*/
public static function read(array $expression, array &$errors = []): ?static
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
// Инициализация выражения поиска
$where = 'WHERE ';
// Инициализация параметров запроса
$params = [];
foreach ($expression as $parameter => $value) {
// Перебор выражения поиска
// Запись в строку запроса
$where .= "`$parameter` = :$parameter &&";
// Запись параметров запроса
$params[":$parameter"] = $value;
}
// Очистка или реинициализация выражения поиска
$where = empty($expression) ? '' : trim(trim($where, '&&'));
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `accounts` $where LIMIT 1");
// Отправка запроса
$request->execute($params);
// Генерация ответа
if ($account = new static($request->fetch(pdo::FETCH_ASSOC))) {
// Найден аккаунт
try {
if ($permissions = static::permissions((int) $account->id, $errors)) {
// Найдены разрешения
// Запись в буфер данных аккаунта
$account->permissions = $permissions;
} else {
// Не найдены разрешения
throw new exception('Не удалось найти и прочитать разрешения');
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
];
}
} else {
// Не найден аккаунт
throw new exception('Не удалось найти аккаунт');
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors['account'][]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return $account ?? null;
}
/**
* Запись или чтение хеша из базы данных
*
* @param int $id Идентификатор аккаунта
* @param int|null $hash Хеш аутентифиакции
* @param string|null $time Время хранения хеша
* @param array &$errors Журнал ошибок
*
* @return array ['hash' => $hash, 'time' => $time]
*/
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)) {
// Переданы хеш и его время хранения
// Инициализация запроса
$request = static::$db->prepare("UPDATE `accounts` SET `hash` = :hash, `time` = :time WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $id,
":hash" => $hash,
":time" => $time,
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
} else {
// Не переданы хеш и его время хранения
// Инициализация запроса
$request = static::$db->prepare("SELECT `hash`, `time` FROM `accounts` WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $id,
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
extract((array) $request->fetch(pdo::FETCH_ASSOC));
if (!empty($response['time']) && $response['time'] <= time()) {
// Истекло время жизни хеша
// Инициализация запроса
$request = static::$db->prepare("UPDATE `accounts` SET `hash` = :hash, `time` = :time WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $id,
":hash" => null,
":time" => null,
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
$response = $request->fetch(pdo::FETCH_ASSOC);
// Генерация ошибки
throw new exception('Время аутентификации истекло');
}
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return ['hash' => $hash, 'time' => $time];
}
}

View File

@@ -1,358 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\surikovlib\models;
use mirzaev\surikovlib\models\accounts_model as accounts;
use pdo;
use exception;
/**
* Модель книг
*
* @package mirzaev\surikovlib\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class books_model extends core
{
/**
* Запись в базу данных
*
* @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'] = [];
try {
// Инициализация аккаунта
$account = accounts::init($account, $errors);
// Инициализация запроса
$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 $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
{
// Инициализация журнала ошибок
$errors['books'] ?? $errors['books'] = [];
try {
// Инициализация выражения поиска
$where = 'WHERE ';
// Инициализация параметров запроса
$params = [];
foreach ($expression as $parameter => $value) {
// Перебор выражения поиска
// Запись в строку запроса
$where .= "`$parameter` = :$parameter &&";
// Запись параметров запроса
$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()
];
}
return [];
}
/**
* Удаление из базы данных
*
* @param int $id Идентификатор
* @param array &$errors Журнал ошибок
*
* @return bool Статус выполнения
*/
public static function delete(int $id, array &$errors = []): bool
{
// Инициализация журнала ошибок
$errors['books'] ?? $errors['books'] = [];
try {
// Инициализация запроса
$request = static::$db->prepare("DELETE FROM `books` WHERE `id` = :id LIMIT 1");
// Отправка запроса
$request->execute([':id' => $id]);
return true;
} catch (exception $e) {
// Запись в журнал ошибок
$errors['books'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return false;
}
/**
* Импорт
*
* @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);
// Инициализация буфера инициализированных книг
$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 int $page Страница (сдвиг)
* @param array &$errors Журнал ошибок
*
* @return bool Статус выполнения
*/
public static function rotate(int $id, int $page = 1, array &$errors = []): bool
{
// Инициализация журнала ошибок
$errors['books'] ?? $errors['books'] = [];
try {
// Инициализация запроса
$request = static::$db->prepare("SELECT EXISTS (SELECT * FROM `books` WHERE `id` = :id LIMIT 1)");
// Отправка запроса
$request->execute([':id' => $id]);
if ($request->fetch(pdo::FETCH_NUM)[0] === 1) {
// Найдена книга
// Инициализация пути книги
$book = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $id;
// Инициализация пути до файла
$file = $book . DIRECTORY_SEPARATOR . "$page.jpg";
// Инициализация пути до нового местоположения файла
$old = $book . DIRECTORY_SEPARATOR . 'old' . DIRECTORY_SEPARATOR . "$page.jpg";
// Инициализация директории
if (!file_exists($book)) throw new exception('Не удалось найти директорию книги');
// Инициализация директории оригинальных изображений
if (!file_exists($book . DIRECTORY_SEPARATOR . 'old')) if (!mkdir($book . DIRECTORY_SEPARATOR . 'old', 0755, true)) throw new exception('Не удалось записать директорию для оригинальных страниц книги');
// Перемещение страницы в директорию оригинальных страниц
exec("mv $file $old");
// Переворачивание файла и возвращение на нужное местоположение
exec("jpegtran -rotate 90 $old > $file");
return true;
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors['books'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return false;
}
/**
* Подсчёт количества страниц
*
* @param int $id Идентификатор
* @param array &$errors Журнал ошибок
*
* @return int|null Количество страниц
*/
public static function amount(int $id, array &$errors = []): ?int
{
// Инициализация журнала ошибок
$errors['books'] ?? $errors['books'] = [];
try {
// Инициализация счётчика
$amount = -1;
while (true) {
// Перебор директорий (!!! Рекурсия !!!)
// Перебор изображений по возрастанию (от 0.jpg до 999.jpg и т.д.)
if (!file_exists(\STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $id . DIRECTORY_SEPARATOR . ++$amount . '.jpg')) return $amount;
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors['books'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
}

View File

@@ -1,139 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\surikovlib\models;
use mirzaev\minimal\model;
use pdo;
use pdoexception;
use exception;
/**
* Ядро моделей
*
* @package mirzaev\surikovlib\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
class core extends model
{
/**
* Соединение с базой данных
*/
protected static PDO $db ;
public function __construct(pdo $db = null)
{
if (isset($db)) {
// Получена инстанция соединения с базой данных
// Запись и инициализация соединения с базой данных
$this->__set('db', $db);
} else {
// Не получена инстанция соединения с базой данных
// Инициализация соединения с базой данных по умолчанию
$this->__get('db');
}
}
/**
* Записать свойство
*
* @param string $name Название
* @param mixed $value Значение
*/
public function __set(string $name, mixed $value = null): void
{
match ($name) {
'db' => (function () use ($value) {
if ($this->__isset('db')) {
// Свойство уже было инициализировано
// Выброс исключения (неудача)
throw new exception('Запрещено реинициализировать соединение с базой данных ($this->db)', 500);
} else {
// Свойство ещё не было инициализировано
if ($value instanceof pdo) {
// Передано подходящее значение
// Запись свойства (успех)
self::$db = $value;
} else {
// Передано неподходящее значение
// Выброс исключения (неудача)
throw new exception('Соединение с базой данных ($this->db) должен быть инстанцией PDO', 500);
}
}
})(),
default => parent::__set($name, $value)
};
}
/**
* Прочитать свойство
*
* @param string $name Название
*
* @return mixed Содержимое
*/
public function __get(string $name): mixed
{
return match ($name) {
'db' => (function () {
if (!$this->__isset('db')) {
// Свойство не инициализировано
// Инициализация значения по умолчанию исходя из настроек
$this->__set('db', new pdo(\TYPE . ':dbname=' . \BASE . ';host=' . \HOST, LOGIN, PASSWORD));
}
return self::$db;
})(),
default => parent::__get($name)
};
}
/**
* Проверить свойство на инициализированность
*
* @param string $name Название
*/
public function __isset(string $name): bool
{
return match ($name) {
default => parent::__isset($name)
};
}
/**
* Удалить свойство
*
* @param string $name Название
*/
public function __unset(string $name): void
{
match ($name) {
default => parent::__isset($name)
};
}
/**
* Статический вызов
*
* @param string $name Название
* @param array $arguments Параметры
*/
public static function __callStatic(string $name, array $arguments): mixed
{
match ($name) {
'db' => (new static)->__get('db'),
default => throw new exception("Не найдено свойство или функция: $name", 500)
};
}
}

View File

@@ -1,105 +0,0 @@
# ----------------------------
# Host config
# ----------------------------
server {
listen %ip%:%httpport%;
listen %ip%:%httpsport% ssl http2;
server_name surikovlib.loc %aliases%;
root '%hostdir%';
limit_conn addr 64;
autoindex off;
index index.php index.html index.htm;
ssl_certificate '%sprogdir%/userdata/config/cert_files/server.crt';
ssl_certificate_key '%sprogdir%/userdata/config/cert_files/server.key';
# ssl_trusted_certificate '';
# Force HTTPS
# add_header Strict-Transport-Security 'max-age=2592000' always;
# if ($scheme ~* ^(?!https).*$) {
# return 301 https://$host$request_uri;
# }
# Force www.site.com => site.com
# if ($host ~* ^www\.(.+)$) {
# return 301 $scheme://$1$request_uri;
# }
# Disable access to backup/config/command/log files
# if ($uri ~* ^.+\.(?:bak|co?nf|in[ci]|log|orig|sh|sql|tar|sql|t?gz|cmd|bat)$) {
# return 404;
# }
# Disable access to hidden files/folders
if ($uri ~* /\.(?!well-known)) {
return 404;
}
# Disable MIME sniffing
add_header X-Content-Type-Options 'nosniff' always;
location ~* ^.+\.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv|svgz?|ttf|ttc|otf|eot|woff2?)$ {
expires 1d;
access_log off;
}
location / {
# Force index.php routing (if not found)
try_files $uri $uri/ /index.php$is_args$args;
# Force index.php routing (all requests)
# rewrite ^/(.*)$ /index.php?/$1 last;
location ~ \.php$ {
try_files $fastcgi_script_name =404;
# limit_conn addr 16;
# limit_req zone=flood burst=32 nodelay;
# add_header X-Frame-Options 'SAMEORIGIN' always;
# add_header Referrer-Policy 'no-referrer-when-downgrade' always;
# CSP syntax: <host-source> <scheme-source>(http: https: data: mediastream: blob: filesystem:) 'self' 'unsafe-inline' 'unsafe-eval' 'none'
# Content-Security-Policy-Report-Only (report-uri https://site.com/csp/)
# add_header Content-Security-Policy "default-src 'self'; connect-src 'self'; font-src 'self'; frame-src 'self'; img-src 'self'; manifest-src 'self'; media-src 'self'; object-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; base-uri 'none'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests" always;
fastcgi_pass backend;
include '%sprogdir%/userdata/config/nginx_fastcgi_params.txt';
}
}
# Service configuration (do not edit!)
# ----------------------------
location /openserver/ {
root '%sprogdir%/modules/system/html';
autoindex off;
index index.php index.html index.htm;
%allow%allow all;
allow 127.0.0.0/8;
allow ::1/128;
allow %ips%;
deny all;
location ~* ^/openserver/.+\.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv|svgz?|ttf|ttc|otf|eot|woff2?)$ {
expires 1d;
access_log off;
}
location /openserver/server-status {
stub_status on;
}
location ~ ^/openserver/.*\.php$ {
try_files $fastcgi_script_name =404;
fastcgi_index index.php;
fastcgi_pass backend;
include '%sprogdir%/userdata/config/nginx_fastcgi_params.txt';
}
}
# End service configuration
# ----------------------------
}
# ----------------------------
# End host config
# ----------------------------

View File

@@ -1,49 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\surikovlib;
use mirzaev\minimal\core;
use mirzaev\minimal\router;
define('VIEWS', realpath('..' . DIRECTORY_SEPARATOR . 'views'));
define('STORAGE', realpath('..' . DIRECTORY_SEPARATOR . 'storage'));
define('TYPE', 'mysql');
define('BASE', 'surikovlib');
define('HOST', '127.0.0.1');
define('LOGIN', 'root');
define('PASSWORD', 'sUrikov_topchik_228!');
ini_set('error_reporting', E_ALL);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
// Автозагрузка
require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
// Инициализация маршрутазитора
$router = new router;
// Запись маршрутов
$router->write('/', 'main', 'index');
$router->write('/account/registration', 'accounts', 'registration', 'POST');
$router->write('/account/authentication', 'accounts', 'authentication', 'POST');
$router->write('/account/deauthentication', 'accounts', 'deauthentication', 'POST');
$router->write('/account/deauthentication', 'accounts', 'deauthentication', 'GET');
$router->write('/books', 'books', 'index', 'GET');
$router->write('/books/$id', 'books', 'index', 'GET');
$router->write('/books/$id/$page', 'books', 'index', 'GET');
$router->write('/books/$id/$page/rotate', 'books', 'rotate', 'POST');
$router->write('/books/$id/delete', 'books', 'delete', 'POST');
$router->write('/storage/books/$id/$file', 'books', 'read', 'GET');
$router->write('/storage/books/write', 'books', 'write', 'POST');
$router->write('/kemenov', 'kemenov', 'index', 'GET');
$router->write('/surikov', 'surikov', 'index', 'GET');
$router->write('/contacts', 'contacts', 'index', 'GET');
// Инициализация ядра
$core = new core(namespace: __NAMESPACE__, router: $router);
// Обработка запроса
echo $core->start();

View File

@@ -1,25 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\surikovlib\views;
use mirzaev\minimal\controller;
use Twig\Loader\FilesystemLoader;
use Twig\Environment as view;
/**
* Менеджер представлений
*
* @package mirzaev\surikovlib\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class manager extends controller
{
public function render(string $file, array $vars = []): ?string
{
// Генерация представления
return (new view(new FilesystemLoader(VIEWS)))->render($file, $vars);
}
}