ksenia ebanula

This commit is contained in:
2025-11-03 22:47:14 +03:00
parent 7fe34899a9
commit 2f43369a65
76 changed files with 5716 additions and 3682 deletions

24
composer.lock generated
View File

@@ -8,11 +8,11 @@
"packages": [ "packages": [
{ {
"name": "mirzaev/languages", "name": "mirzaev/languages",
"version": "1.0.2", "version": "1.0.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://git.svoboda.works/mirzaev/languages", "url": "https://git.svoboda.works/mirzaev/languages",
"reference": "eceff49204c718243f24e3da42294c5ea5b29e01" "reference": "c6e28f25ea1bf42f4f8a201c2919c02fef59a284"
}, },
"require": { "require": {
"php": "^8.4" "php": "^8.4"
@@ -38,22 +38,20 @@
"description": "Library for easy languages support", "description": "Library for easy languages support",
"homepage": "https://git.svoboda.works/mirzaev/languages", "homepage": "https://git.svoboda.works/mirzaev/languages",
"keywords": [ "keywords": [
"enumeration",
"languages" "languages"
], ],
"support": { "support": {
"issues": "https://git.svoboda.works/mirzaev/languages/issues", "issues": "https://git.svoboda.works/mirzaev/languages/issues"
"wiki": "https://git.svoboda.works/mirzaev/languages/wiki"
}, },
"time": "2025-08-21T14:50:06+00:00" "time": "2025-10-21T18:34:30+00:00"
}, },
{ {
"name": "mirzaev/minimal", "name": "mirzaev/minimal",
"version": "3.6.2", "version": "3.7.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://git.svoboda.works/mirzaev/minimal", "url": "https://git.svoboda.works/mirzaev/minimal",
"reference": "d9e4e0af6cffc169831eec798d00e53187839b8e" "reference": "e604d19eb1a39049bf51e9318cbda952f89e949a"
}, },
"require": { "require": {
"php": "~8.4" "php": "~8.4"
@@ -76,18 +74,18 @@
"role": "Programmer" "role": "Programmer"
} }
], ],
"description": "My vision of a good framework", "description": "The best code-to-utility framework",
"homepage": "https://git.mirzaev.sexy/mirzaev/minimal", "homepage": "https://git.svoboda.works/mirzaev/minimal",
"keywords": [ "keywords": [
"framework", "framework",
"lightweight", "lightweight",
"mvc" "mvc"
], ],
"support": { "support": {
"docs": "https://git.mirzaev.sexy/mirzaev/minimal/wiki", "docs": "https://git.svoboda.works/mirzaev/minimal/wiki",
"issues": "https://git.mirzaev.sexy/mirzaev/minimal/issues" "issues": "https://git.svoboda.works/mirzaev/minimal/issues"
}, },
"time": "2025-07-16T01:09:25+00:00" "time": "2025-10-25T11:39:20+00:00"
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",

View File

@@ -112,7 +112,8 @@ final class accounts extends core
} }
} else { } else {
// Not registered // Not registered
var_dump($this->errors); die; var_dump($this->errors);
die;
// Sending response // Sending response
$this->response $this->response
->status(status::unauthorized) ->status(status::unauthorized)

View File

@@ -47,16 +47,20 @@ final class books extends core
* *
* @param int|string|null $identifier Identifier of the book * @param int|string|null $identifier Identifier of the book
* @param int|string $page Page if the book * @param int|string $page Page if the book
* * @param int|string $next Next page if the book
* @return null * @return null
*/ */
public function index( public function index(
int|string|null $identifier = null, int|string|null $identifier = null,
int|string $page = 1 int|string $page = 1,
int|string $next = 0,
int|string $last = 0
): null { ): null {
// Normalizing the page argument // Normalizing the page argument
$page = (int) $page; $page = (int) $page;
if ($page < 1) $page = 1; if ($page < 1) {
$page = 1;
}
if (isset($identifier)) { if (isset($identifier)) {
// Received the book identifier (the book) // Received the book identifier (the book)
@@ -64,11 +68,30 @@ final class books extends core
// Initializing the book // Initializing the book
$this->view->book = book::read(expressions: ['identifier' => (int) $identifier])[0] ?? null; $this->view->book = book::read(expressions: ['identifier' => (int) $identifier])[0] ?? null;
if (empty($page)) { if (!empty($page)) {
// Received the book page // Received the book page
// Initializing the book page // Initializing the book page
$this->view->page = $page; $this->view->page = $page;
//Вынести в модель
$next = $page + 1;
$directory = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $identifier . DIRECTORY_SEPARATOR . $next . '.jpg';
if (!file_exists($directory)) {
$next = $page;
}
$last = $page - 1;
$directory = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $identifier . DIRECTORY_SEPARATOR . $last . '.jpg';
if (!file_exists($directory)) {
$last = $page;
}
$this->view->next = $next;
$this->view->last = $last;
} }
if (str_contains($this->request->headers['accept'] ?? '', content::any->value)) { if (str_contains($this->request->headers['accept'] ?? '', content::any->value)) {
@@ -136,10 +159,12 @@ final class books extends core
*/ */
public function write(): null public function write(): null
{ {
if (account::initialize(errors: $this->errors['account'])->access('books')) { if ($this->account->access('books')) {
// Initialized account and authorized to books // Initialized account and authorized to books
if (count($books = book::import(files: $this->request->files['books'] ?? [], errors: $this->errors['books'])) > 0) { if (count($books = book::import(files: $this->request->files['books'] ?? [], errors: $this->errors['books'])) > 0) {
$status = true;
// Imported books // Imported books
} else { } else {
// Not imported books // Not imported books
@@ -172,26 +197,25 @@ final class books extends core
// Normalizing the page argument // Normalizing the page argument
$page = (int) $page; $page = (int) $page;
if ($page < 1) $page = 1; if ($page < 1) {
$page = 1;
}
// Initializing the book file path // Initializing the publication file path
$file = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $identifier . DIRECTORY_SEPARATOR . $page . '.jpg'; $file = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $identifier . DIRECTORY_SEPARATOR . $page . '.jpg';
// Reading the file
$stream = fopen($file, 'rb');
if (file_exists($file)) { if (file_exists($file)) {
// Found the book page file // Found the publication page file
// Initializing headers // Initializing headers
header('Content-Description: File Transfer'); header('Content-Type: image/jpg');
header('Content-Type: image/jpeg');
header('Content-Disposition: attachment; filename=' . basename($file));
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($file)); header('Content-Length: ' . filesize($file));
// Cleaning the output buffer // Cleaning the output buffer
ob_end_clean(); fpassthru($stream);
// Exit (success)
return file_get_contents($file);
} }
// Exit (fail) // Exit (fail)
@@ -207,7 +231,7 @@ final class books extends core
* *
* @return null * @return null
*/ */
public function delete(int|string|null $identifier = null): null public function delete(int|string|null $identifier = null, int $active = 0): null
{ {
if (account::initialize(errors: $this->errors['account'])->access('books')) { if (account::initialize(errors: $this->errors['account'])->access('books')) {
// Initialized account and authorized to books // Initialized account and authorized to books
@@ -218,22 +242,14 @@ final class books extends core
// Normalizing the identifier argument // Normalizing the identifier argument
$identifier = (int) $identifier; $identifier = (int) $identifier;
if (book::delete(identifier: $identifier, errors: $this->errors['books'])) { if (book::delete(identifier: $identifier, active: $active, errors: $this->errors['books'])) {
// Deleted the book from the database // Deleted the book from the database
// Initializing the book directory path // Initializing the book directory path
$directory = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $identifier; $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; $status = true;
} }
}
if (str_contains($this->request->headers['accept'] ?? '', content::json->value)) { if (str_contains($this->request->headers['accept'] ?? '', content::json->value)) {
// Request for JSON response // Request for JSON response

View File

@@ -41,6 +41,13 @@ class core extends controller
*/ */
protected language $language = language::ru; protected language $language = language::ru;
/**
* Account
*
* @var account $account Account
*/
// protected account $account;
protected ?account $account = null;
/** /**
* Response * Response
* *
@@ -74,15 +81,21 @@ class core extends controller
public function __construct(minimal $core) public function __construct(minimal $core)
{ {
// Blocking requests from CloudFlare (better to write this blocking into nginx config file) // 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; if (isset($_SERVER['HTTP_USER_AGENT']) && $_SERVER['HTTP_USER_AGENT'] === 'nginx-ssl early hints')
return status::bruh->label;
// Initializing the models core // Initializing the models core
new models(); new models();
$this->account = account::initialize(errors: $this->errors['account']);
// Initializing the view template engine instance // Initializing the view template engine instance
$this->view = new templater(account::initialize(errors: $this->errors['account'])); $this->view = new templater($this->account);
// For the extends system // For the extends system
parent::__construct(core: $core); parent::__construct(core: $core);
} }
} }

View File

@@ -6,7 +6,8 @@ namespace kodorvan\surikov\controllers;
// Files of the project // Files of the project
use kodorvan\surikov\controllers\core, use kodorvan\surikov\controllers\core,
kodorvan\surikov\models\account; kodorvan\surikov\models\account,
kodorvan\surikov\models\publication;
// Framework for PHP // Framework for PHP
use mirzaev\minimal\http\enumerations\content, use mirzaev\minimal\http\enumerations\content,
@@ -33,16 +34,20 @@ final class index extends core
*/ */
protected array $errors = [ protected array $errors = [
'system' => [], 'system' => [],
'account' => [] 'account' => [],
];
];
var_dump($this->request->headers['accept']);
die;
/** /**
* Main page * Main page
* *
* @return null * @return null
*/ */
public function index(): null public function index(): null
{ {
if (str_contains($this->request->headers['accept'] ?? '', content::any->value)) { if (str_contains($this->request->headers['accept'] ?? '', content::any->value)) {
// Request for any response // Request for any response
@@ -70,3 +75,32 @@ final class index extends core
return null; 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,446 @@
<?php
declare(strict_types=1);
namespace kodorvan\surikov\controllers;
// The project
use kodorvan\surikov\controllers\core,
kodorvan\surikov\models\account,
kodorvan\surikov\models\publication;
// Framework for PHP
use mirzaev\minimal\http\enumerations\content,
mirzaev\minimal\http\enumerations\status;
/**
* Publications controller
*
* @package kodorvan\surikov\controllers
*
* @param array $errors Registry of errors
*
* @method null index() Publications 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 publications extends core
{
/**
* Errors
*
* @var array $errors Registry of errors
*/
protected array $errors = [
'system' => [],
'account' => [],
'publications' => []
];
/**
* Data verification
*
* @param string|null $title Title if the publication
* @param string|null $content Content if the publication
*
* @return null
*/
private function data(string|null $title, string|null $content): bool
{
//Removing spaces
$title = trim($title ?? '');
$content = trim($content ?? '');
//Checking for the existence of data
if (empty($title)) {
//Error recording
$this->errors['publications'][] = 'Заголовок не может быть пустым';
// Exit (failed)
return false;
}
//Removing the shielding "/"
$title = stripslashes($title);
$content = stripslashes($content);
//Checking the length
if (mb_strlen($title) > 255) {
//Error recording
$this->errors['publications'][] = 'Заголовок слишком длинный';
// Exit (failed)
return false;
}
//Checking the length
if (mb_strlen($content) > 1000) {
//Error recording
$this->errors['publications'][] = 'Содержание слишком большое!';
// Exit (failed)
return false;
}
// Exit (success)
return true;
}
/**
* Processing of uploaded files
*
* @return array
*/
private function files($identifier): array|null
{
//Presence of uploaded files
if (!empty($_FILES['publications']['tmp_name'])) {
//Array for storing data
$files = [];
// Path to the directory to save
$dir = \STORAGE . DIRECTORY_SEPARATOR . 'publications' . DIRECTORY_SEPARATOR;
//Maximum file size (5MB)
$size = 5 * 1024 * 1024;
// Acceptable MIME types
$types = ['image/jpeg', 'image/png', 'image/gif'];
//Checking if the directory exists and creating it if it doesn't
if (!is_dir($dir) && !mkdir($dir, 0755, true)) {
$this->errors['publications'][] = 'Не удалось создать путь для изображения';
// Exit (failed)
return null;
}
//Verifying write permissions to a directory
if (!is_writable($dir)) {
$this->errors['publications'][] = 'Директория недоступна для записи';
// Exit (failed)
return null;
}
// Processing of each file
foreach ($_FILES['publications']['tmp_name'] as $key => $tmp) {
/// Checking the upload to a temporary server
if (empty($tmp) || $_FILES['publications']['error'][$key] !== UPLOAD_ERR_OK) {
$this->errors['publications'][] = 'Ошибка загрузки файла ';
continue;
}
// Checking the file size
if ($_FILES['publications']['size'][$key] > $size) {
$this->errors['publications'][] = 'Размер изображения не должен превышать 5MB';
continue;
}
//// Defining the MIME type of a file
$type = mime_content_type($tmp);
// Checking the file type validity
if (!in_array($type, $types)) {
$this->errors['publications'][] = 'Недопустимый тип файла';
continue;
}
// Full path to save
$path = trim($dir . $identifier . '.png');
//// Moving a file from a temporary directory
if (!move_uploaded_file($tmp, $path)) {
$this->errors['publications'][] = 'Ошибка при загрузке файла на сервер: ' . $_FILES['publications']['name'][$key];
continue;
}
// checking the existence of the file after moving
if (!file_exists($path)) {
$this->erroFrs['publications'][] = 'Файл не был сохранен: ' . $identifier;
continue;
}
// Collecting information about a file
$files[] = [
'path' => $path,
'name' => $identifier,
'size' => $_FILES['publications']['size'][$key],
'type' => $type
];
}
//Exit (Succes)
return $files;
}
//Exit (Failed)
return [];
}
/**
* Read
*
* Read the publication image
*
* @param int|string|null $identifier Identifier of the publication
*
* @return string|null The publication image
*/
public function read(int|string|null $identifier = null): ?string
{
// Normalizing the identifier argument
$identifier = (int) $identifier;
// Initializing the publication file path
$file = \STORAGE . DIRECTORY_SEPARATOR . 'publications' . DIRECTORY_SEPARATOR . $identifier . '.png';
// Reading the file
$stream = fopen($file, 'rb');
if (file_exists($file)) {
// Found the publication page file
// Initializing headers
header('Content-Type: image/png');
header('Content-Length: ' . filesize($file));
// Cleaning the output buffer
fpassthru($stream);
}
// Exit (fail)
return null;
}
/**
* Publications page
*
* @param int|string $page Page if the publication
*
* @return null
*/
public function index(
int|string $page = 1,
): null {
// Reading publications
$this->view->publications = publication::read(expressions: ['active' => true], limit: 30, page: $page, sort: '`created` DESC');
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;
}
}
/**
* Write
*
* Import publications into the storage and the database
* @param string|null $title Title if the publication
* @param string|null $content Content if the publication
* @param int|bool $active Relevance if the publication
*
* @return null
*/
public function write(string|null $title = null, string|null $content = null, int|bool $active = 1): null
{
// Initialized account and authorized to publications
if ($this->account->access('publications')) {
//Data verification
if ($this->data($title, $content)) {
$identifier = 0;
//Processing uploaded files
$identifier = publication::write($title, $content, $active, $this->account->identifier, $this->errors['publications']);
if ($identifier) {
// Recording a publication
$status = true;
//Image verification
$this->files($identifier);
}
} else {
$status = false;
// Data verification failed
}
}
// Reading publications
$this->view->publication = publication::read(expressions: ['account' => (int) $this->account->identifier], sort: '`created` DESC')[0] ?? null;
// Redirecting
$publications = $this->view->render('publication.html');
// Sending response
$this->response
->start()
->clean()
->sse()
->write($publications)
->validate($this->request)
?->body()
->end();
// Deinitializing rendered page
unset($publications);
// Exit (success)
return null;
}
/**
* Update
*
* Update a post in the database and images
*
* @param string|null $title Post title
* @param string|null $content Post content
* @param int|string|null $identifier Post identifier
*
* @return null
*/
public function update(string|null $title = null, string|null $content = null, int|string|null $identifier = null): null
{
// Initialized account and authorized to publications
if ($this->account->access('publications')) {
//Converting an identifier to Int
$identifier = (int) $identifier;
//Data verification
if ($this->data($title, $content) && $identifier > 0) {
//Image verification
$files = $this->files($identifier);
if (publication::update($identifier, $title, $content, $files, $this->errors['publications'])) {
$status = true;
//Updating the publication
} else {
$status = false;
//Not updating the publication
}
} else {
$status = false;
//Data verification failed
}
}
// Reading publications
$this->view->publication = publication::read(expressions: ['account' => (int) $this->account->identifier], sort: '`created` DESC')[0] ?? null;
// Redirecting
$publications = $this->view->render('publication.html');
// Sending response
$this->response
->start()
->clean()
->sse()
->write($publications)
->validate($this->request)
?->body()
->end();
// Deinitializing rendered page
unset($publications);
// Exit (success)
return null;
}
/**
* Delete
*
* Delete the publication from the storage and the database
*
* @param int|string|null $identifier Identifier of the publication
* @param int|bool $active Relevance if the publication
*
* @return null
*/
public function delete(int|string|null $identifier = null, int|bool $active = 0): null
{
// Initialized account and authorized to publications
if ($this->account->access('publications')) {
//Converting an identifier to Int
$identifier = (int) $identifier;
//Verification identifier
if ($identifier > 0) {
if (publication::delete($identifier, $active, $this->errors['publications'])) {
$status = true;
//Deleting publication
} else {
$status = false;
//Not Deleting publication
}
} else {
$status = false;
//Data verification failed
}
}
// Reading publications
$this->view->publication = publication::read(expressions: ['account' => (int) $this->account->identifier], sort: '`created` DESC')[0] ?? null;
// Redirecting
$publications = $this->view->render('publication.html');
// Sending response
$this->response
->start()
->clean()
->sse()
->write($publications)
->validate($this->request)
?->body()
->end();
// Deinitializing rendered page
unset($publications);
// Exit (success)
return null;
}
}

View File

@@ -97,7 +97,8 @@ final class account extends core
// Iterating over parameters // Iterating over parameters
// Writing the property // Writing the property
if (property_exists($this, $key)) $this->$key = $value; if (property_exists($this, $key))
$this->$key = $value;
} }
} }
@@ -132,6 +133,7 @@ final class account extends core
// Authentication // Authentication
$account = static::authenticate(mail: $mail, password: $password, remember: true, errors: $errors); $account = static::authenticate(mail: $mail, password: $password, remember: true, errors: $errors);
} }
// Exit (success) // Exit (success)
@@ -338,10 +340,12 @@ final class account extends core
throw new exception('Вы аутентифицированы с другого устройства (не совпадают хеши аутентификации)'); throw new exception('Вы аутентифицированы с другого устройства (не совпадают хеши аутентификации)');
} }
if (empty($account = static::read([ if (
empty($account = static::read([
'identifier' => $_COOKIE['identifier'], 'identifier' => $_COOKIE['identifier'],
'hash' => $_COOKIE['hash'] 'hash' => $_COOKIE['hash']
]))) { ]))
) {
// Not found the account or a connection with it // Not found the account or a connection with it
// Exit (fail) // Exit (fail)
@@ -477,16 +481,21 @@ final class account extends core
$parameters = []; $parameters = [];
// Filtering the parameter // Filtering the parameter
if (filter_var($mail, FILTER_VALIDATE_EMAIL) === false) throw new exception('Не удалось распознать почту'); if (filter_var($mail, FILTER_VALIDATE_EMAIL) === false)
if (iconv_strlen($mail) < 3) throw new exception('Длина почты должна быть не менее 3 символов'); throw new exception('Не удалось распознать почту');
if (iconv_strlen($mail) > 60) throw new exception('Длина почты должна быть не более 80 символов'); if (iconv_strlen($mail) < 3)
throw new exception('Длина почты должна быть не менее 3 символов');
if (iconv_strlen($mail) > 60)
throw new exception('Длина почты должна быть не более 80 символов');
// Writing the parameter // Writing the parameter
$parameters[':mail'] = $mail; $parameters[':mail'] = $mail;
// Filtering the parameter // Filtering the parameter
if (iconv_strlen($password) < 3) throw new exception('Длина пароля должна быть не менее 3 символов'); if (iconv_strlen($password) < 3)
if (iconv_strlen($password) > 60) throw new exception('Длина пароля должна быть не более 120 символов'); throw new exception('Длина пароля должна быть не менее 3 символов');
if (iconv_strlen($password) > 60)
throw new exception('Длина пароля должна быть не более 120 символов');
// Writing the parameter // Writing the parameter
$parameters[':password'] = password_hash($password, PASSWORD_BCRYPT); $parameters[':password'] = password_hash($password, PASSWORD_BCRYPT);

View File

@@ -149,14 +149,16 @@ final class book extends core
* *
* @return bool Is the book was deleted? * @return bool Is the book was deleted?
*/ */
public static function delete(int $identifier, array &$errors = []): bool public static function delete(int $identifier, int $active, array &$errors = []): bool
{ {
try { try {
// Initializing the request // Initializing the request
$request = static::$database->prepare("DELETE FROM `books` WHERE `identifier` = :identifier LIMIT 1"); $request = static::$database->prepare("UPDATE `books` SET `active` = :active WHERE `identifier` = :identifier LIMIT 1");
// Sending the request // Sending the request
$request->execute([':identifier' => $identifier]); $request->execute([
':identifier' => $identifier,
':active' => $active,
]);
// Exit (success) // Exit (success)
return true; return true;
@@ -190,6 +192,7 @@ final class book extends core
public static function import(array $files, ?int $account = null, array &$errors = []): array|false public static function import(array $files, ?int $account = null, array &$errors = []): array|false
{ {
try { try {
if (empty($files)) { if (empty($files)) {
// Not received books // Not received books
@@ -251,7 +254,7 @@ final class book extends core
exec("pdfimages -j '$file' '$directory'"); exec("pdfimages -j '$file' '$directory'");
// Renaming files // Renaming files
exec("echo 'export j=-1; for i in $directory*.jpg; do let j+=1; mv \$i $directory\$j.jpg; done' | bash"); exec("echo 'export j=0; for i in $directory*.jpg; do let j+=1; mv \$i $directory\$j.jpg; done' | bash");
// Writing into the buffer of writed books // Writing into the buffer of writed books
$writed[] = $identifier; $writed[] = $identifier;
@@ -363,11 +366,11 @@ final class book extends core
'stack' => $e->getTrace() 'stack' => $e->getTrace()
]; ];
} }
// Exit (fail) // Exit (fail)
return false; return false;
} }
/** /**
* Amount * Amount
* *

View File

@@ -0,0 +1,252 @@
<?php
declare(strict_types=1);
namespace kodorvan\surikov\models;
// Built-in libraries
use pdo,
exception;
/**
* Publication model
*
* @package kodorvan\surikov\models
*
* @param
*
* @method static int|null write(string $title, ?string $content, int $active, 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 publication extends core
{
/**
* Write
*
* Write into the database
*
* @param string $title Title
* @param string|null $content Сontent
* @param int $active Active of the publication
* @param int $account Identifier of the publication
* @param array &$errors Registry of errors
*
* @return int|bool The book publication
*/
public static function write(string $title, ?string $content, int $active, int $account, array &$errors = []): int|bool
{
try {
//Current date
$time = date("Y-m-d H:i:s");
// Initializing the request
$request = static::$database->prepare("INSERT INTO `publications` (`account`, `title`, `content`, `updated`, `created`,`active`) VALUES (:account, :title, :content, :updated, :created, :active) ;");
// Initializing the request parameters
$parameters = [
':account' => $account,
':title' => $title,
':content' => $content,
':updated' => $time,
':created' => $time,
':active' => $active
];
// Sending the request
$request->execute($parameters);
// Initializing the request
$request = static::$database->prepare("SELECT `identifier` FROM `publications` WHERE `account` = :account AND `created` = :created ORDER BY `identifier` DESC LIMIT 1");
// Initializing the request parameters
$request->execute([
':account' => $account,
':created' => $time
]);
//Getting the identifier
$result = $request->fetch(PDO::FETCH_ASSOC);
//Exit (true)
return (int) $result['identifier'];
} 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;
}
/**
* Read
*
* Read publication from the database
*
* @param array $expressions Request expressions
* @param string $sort Sort
* @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 = [], string $sort = '', 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, '&&'));
//Forming a query sort
$order = empty($sort) ? '' : "ORDER BY $sort";
// Initializing the page
$page = $limit * --$page;
// Initializing the request
$request = static::$database->prepare("SELECT * FROM `publications` $row $order 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;
}
/**
* Update
*
* Update the database from the database
*
* @param int $identifier Identifier
* @param string $title Title
* @param string $content Content
*
* @param array &$errors Registry of errors
*
* @return bool Is the publications was deleted?
*/
public static function update(int $identifier, string $title, ?string $content, ?array $files, array &$errors = []): ?bool
{
try {
// Initializing the request
$request = static::$database->prepare("UPDATE `publications` SET `title` = :title, `content` = :content WHERE `identifier` = :identifier ");
// Initializing the request parameters
$parameters = [
':title' => $title,
':content' => $content,
':identifier' => $identifier,
];
// Sending the request
$request->execute($parameters);
// 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;
}
/**
* Delete
*
* Delete the publication from the database
*
* @param int $identifier Identifier
* @param int $active Active
* @param int $limit Amount
*
* @param array &$errors Registry of errors
*
* @return bool Is the publication was deleted?
*/
public static function delete(int $identifier, int $active, array &$errors = []): bool
{
try {
// Initializing the request
$request = static::$database->prepare("UPDATE `publications` SET `active` = :active WHERE `identifier` = :identifier LIMIT 1");
// Initializing the request parameters
$parameters = [
':identifier' => $identifier,
':active' => $active,
];
// Sending the request
$request->execute($parameters);
// 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;
}
}

View File

@@ -20,17 +20,17 @@ main>section#books>form.upload>p {
} }
main > section#books > article.book { main > section#books > article.book {
width: calc(100% / 3 - 20px);
height: 220px;
position: relative; position: relative;
margin-right: 20px; margin-right: 20px;
width: calc(100% / 3 - 20px);
height: 220px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border-radius: 3px;
overflow: hidden; overflow: hidden;
background: #b38b8b; border-radius: 3px;
transition: .1s ease-in-out;
font-weight: 900; font-weight: 900;
background-color: #b38b8b;
transition: 0.1s ease-in-out;
} }
main > section#books > article.book:hover { main > section#books > article.book:hover {
@@ -53,23 +53,23 @@ main>section#books>article.book>button {
border-radius: 3px; border-radius: 3px;
border: none; border: none;
color: #ff5757; color: #ff5757;
background-color: rgba(0, 0, 0, .5); background-color: rgba(0, 0, 0, 0.5);
} }
main > section#books > article.book:hover > button { main > section#books > article.book:hover > button {
opacity: 1; opacity: 1;
pointer-events: all; pointer-events: all;
transition: .2s ease-in-out; transition: 0.2s ease-in-out;
} }
main > section#books > article.book > button:hover { main > section#books > article.book > button:hover {
color: #ff6b6b; color: #ff6b6b;
background-color: rgba(0, 0, 0, .4); background-color: rgba(0, 0, 0, 0.4);
} }
main > section#books > article.book > button:active { main > section#books > article.book > button:active {
color: #e04d4d; color: #e04d4d;
background-color: rgba(0, 0, 0, .6); background-color: rgba(0, 0, 0, 0.6);
} }
main > section#books > article.book:nth-child(3n) { main > section#books > article.book:nth-child(3n) {
@@ -84,8 +84,9 @@ main>section#books>:is(form.upload, article.book):nth-last-child(3) {
main > section#books > article.book > img { main > section#books > article.book > img {
height: 100%; height: 100%;
width: auto;
object-fit: cover; object-fit: cover;
object-position: right; /* object-position: right; */
text-align: center; text-align: center;
/* overflow: hidden; */ /* overflow: hidden; */
/* clip-path: polygon(5px calc(100% - 5px), calc(100% - 5px) calc(100% - 5px), calc(100% - 5px) 5px, 5px 5px); */ /* clip-path: polygon(5px calc(100% - 5px), calc(100% - 5px) calc(100% - 5px), calc(100% - 5px) 5px, 5px 5px); */
@@ -104,7 +105,7 @@ main>section#books>article.book>h4 {
-ms-hyphens: auto; -ms-hyphens: auto;
hyphens: auto; hyphens: auto;
color: #e8e8e8; color: #e8e8e8;
background-color: rgba(0, 0, 0, .5); background-color: rgba(0, 0, 0, 0.5);
} }
main > section#books > article.book > h4:hover { main > section#books > article.book > h4:hover {
@@ -121,7 +122,7 @@ main>section#books>article.book>p {
main > section#book > img, main > section#book > img,
main > section#book > img::before { main > section#book > img::before {
margin-bottom: 20px; padding-bottom: 20px;
width: 100%; width: 100%;
} }
@@ -134,9 +135,15 @@ main>section#book>img::before{
} }
main > section#book > nav > ul { main > section#book > nav > ul {
margin: 0; display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 15px;
width: 200px;
justify-items: center;
align-items: center;
margin: 0 auto;
padding: 0; padding: 0;
display: flex;
list-style: none; list-style: none;
} }
@@ -149,9 +156,11 @@ main>section#book>nav>ul>li:only-of-type {
margin-right: auto; margin-right: auto;
} }
main > section#book > nav > ul > li { main > section#book > nav > ul > li {
margin-right: 15px; /* margin-right: 15px; */
display: flex;
align-items: center;
justify-content: center;
} }
main > section#book > nav > ul > li:first-of-type, main > section#book > nav > ul > li:first-of-type,
@@ -174,10 +183,20 @@ main>section#book>nav>ul>li.icon>i {
color: #9b3d10; color: #9b3d10;
} }
main>section#book>nav>ul>li.icon>i:hover { main > section#book > nav > ul > li.icon > i:hover,
main > section#book > nav > ul > li > a > i:hover {
color: #bd4f1c; color: #bd4f1c;
} }
main > section#book > nav > ul > li.icon > i:active { main > section#book > nav > ul > li.icon > i:active {
color: #813410; color: #813410;
} }
main > section#book > nav > ul > li.middle {
grid-column: 2;
}
main > section#book > nav > ul > li.previous {
grid-column: 1;
}
main > section#book > nav > ul > li.next {
N FgHJ.,./

View File

@@ -0,0 +1,44 @@
@charset "UTF-8";
i.icon.arrow:not(.circle, .square) {
position: relative;
width: 22px;
height: 22px;
display: block;
box-sizing: border-box;
transform: rotate(var(--rotate));
}
i.icon.arrow.short:not(.circle, .square) {
width: 10px;
}
i.icon.arrow:not(.circle, .square)::after,
i.icon.arrow:not(.circle, .square)::before {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
right: 3px;
}
i.icon.arrow:not(.circle, .square)::after {
width: 8px;
height: 8px;
border-top: 2px solid;
border-right: 2px solid;
transform: rotate(45deg);
bottom: 7px;
}
i.icon.arrow.small:not(.circle, .square)::after {
width: 7px;
height: 7px;
}
i.icon.arrow:not(.circle, .square, .short)::before {
width: 16px;
height: 2px;
bottom: 10px;
background-color: currentColor;
}

View File

@@ -1,9 +1,11 @@
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:wght@200;400;500;600;700&display=swap'); @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:wght@200;400;500;600;700&display=swap');
--rotate: 180deg;
* { * {
font-family: 'IBM Plex Serif', serif; font-family: 'IBM Plex Serif', serif;
text-decoration: none; text-decoration: none;
transition: .2s ease-out; transition: .2s ease-out;
} }
button, button,
@@ -22,7 +24,7 @@ body {
display: grid; display: grid;
grid-template-rows: auto auto 150px; grid-template-rows: auto auto 150px;
/* grid-template-columns: minmax(100px, auto) 300px minmax(500px, auto) minmax(100px, auto); */ /* grid-template-columns: minmax(100px, auto) 300px minmax(500px, auto) minmax(100px, auto); */
grid-template-columns: minmax(100px, auto) 300px minmax(500px, 1000px) minmax(100px, auto); grid-template-columns: minmax(100px, auto) 300px minmax(270px, 600px) minmax(100px, auto);
background-color: #e5ddd1; background-color: #e5ddd1;
} }

View File

@@ -0,0 +1,147 @@
.content-main {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.add-new {
display: flex;
flex-direction: column;
justify-content: left;
padding: 1rem;
box-sizing: border-box;
height: auto;
border-radius: 0.25rem;
background-color: #d9b5b5;
}
.add-new > input {
font-size: 0.9em;
font-weight: 500;
margin-bottom: 0.5rem;
height: 1rem;
}
.add-new > textarea {
min-height: 2rem;
max-height: 5rem;
max-width: 100%;
resize: vertical;
padding: 1rem 0.5rem;
margin-bottom: 1rem;
border: none;
border-radius: 0.25rem;
outline: none;
font-size: 0.8rem;
font-weight: 300;
color: black;
}
.add-new > textarea:focus {
border: none;
outline: none;
}
.custom-file-upload {
display: flex;
gap: 1rem;
align-items: center;
}
.custom-file-upload > .file-input {
display: none;
}
.custom-file-upload > .custom-file-button {
cursor: pointer;
}
.new-btn > button {
padding: 0.4rem 0.6rem;
cursor: pointer;
text-align: center;
font-size: 0.8em;
font-weight: 300;
border: unset;
border-radius: 3px;
color: #fdfdfd;
background-color: #ad4717;
}
.new-btn > button:hover {
color: #fff;
background-color: #c5531f;
}
.content-news {
display: flex;
flex-direction: column;
box-sizing: border-box;
width: 100%;
gap: 2rem;
}
.content-news > .block-new {
display: flex;
gap: 1.5rem;
display: inline-flex;
}
.content-news > .block-new > .new-inside {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.content-news > .block-new > .new-inside > img {
width: 100%;
object-fit: cover;
border-radius: 0.4rem;
max-height: 35rem;
display: block;
max-width: 100%;
}
.content-news > .block-new > .new-inside > .new-title {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
}
.content-news > .block-new > .new-inside > .new-title > .right-block {
display: flex;
flex-direction: column;
}
.content-news > .block-new > .new-inside > .new-title > h1 {
font-family: "Pochaevsk", system-ui;
font-size: 3em;
margin: unset;
line-height: 3rem;
}
.new-btns {
display: inline-flex;
justify-content: right;
}
.new-btns > button {
background: unset;
border: unset;
margin: unset;
}
.new-btns > #edit:hover {
color: #61b128;
}
.new-btns > #trash:hover {
color: #ad4717;
}

View File

@@ -0,0 +1,288 @@
@import url("https://fonts.googleapis.com/css2?family=Cormorant:ital,wght@0,300..700;1,300..700&family=Geologica:wght@100..900&family=Lora:ital,wght@0,400..700;1,400..700&family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Pochaevsk&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Cormorant:ital,wght@0,300..700;1,300..700&family=Geologica:wght@100..900&family=Lora:ital,wght@0,400..700;1,400..700&family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap");
* {
font-family: "Geologica", sans-serif;
text-decoration: none;
transition: 0.2s ease-out;
}
button,
*[type="button"] {
cursor: pointer;
font-weight: 900;
}
::selection,
::-moz-selection {
background-color: #544f7f;
}
body {
margin: 0;
display: grid;
grid-template-rows: auto auto 150px;
/* grid-template-columns: minmax(100px, auto) 300px minmax(500px, auto) minmax(100px, auto); */
grid-template-columns: minmax(100px, auto) 270px minmax(270px, 600px) minmax(
100px,
auto
);
background-color: #e5ddd1;
}
a {
color: unset;
}
.content {
display: flex;
padding: 0px 20vw;
}
aside {
margin-right: 20px;
grid-column: 2;
}
aside > section {
margin-bottom: 15px;
}
aside > section > h3 {
margin-top: -0.2rem;
text-align: center;
}
:is(main, aside) {
margin-top: 30px;
display: inline-block;
grid-row: 2;
}
:is(main, aside) > section {
border-radius: 3px;
background-color: #d9b5b5;
}
:is(main, aside) > section {
padding: 20px;
}
main {
grid-column: 3;
}
main > section > h2 {
margin-top: 0;
margin-bottom: 2rem;
text-align: center;
font-size: 1.8rem;
}
/* main>section>h2:has(+ .divider) { */
main > section > h2 + .divider {
margin-top: -1rem;
}
header {
z-index: 1000;
margin-bottom: 30px;
grid-row: 1;
}
header > .menu {
z-index: 1000;
width: 100%;
height: 250px;
position: absolute;
display: flex;
}
header > .menu > nav {
top: 0;
margin: 0 auto;
height: 40px;
padding: 15px 0;
position: sticky;
display: flex;
}
header > .menu > nav > #logo {
height: inherit;
}
header > .menu > nav > #logo > img {
height: inherit;
position: relative;
}
header > .menu > nav > #logo ~ .link {
margin-right: unset;
margin-left: 50px;
}
header > .menu > nav > .link {
margin: auto 0;
margin-right: 50px;
text-decoration: none;
color: #e5ddd1;
font-weight: 900;
}
header > .menu > nav > .link:last-child {
margin-right: auto;
}
header > .window {
z-index: 500;
height: 650px;
position: relative;
display: flex;
padding: 30px;
overflow: hidden;
background-color: #1a1449;
}
header > .window > .background {
margin-top: auto;
width: 100%;
height: min-content;
}
header > .window > .img_1 {
left: -70px;
bottom: -20px;
height: 120%;
position: absolute;
}
header,
footer {
grid-column-start: 1;
grid-column-end: 5;
}
header > nav,
footer {
background-color: #1a1449;
}
footer {
z-index: 800;
margin-top: 30px;
width: 100%;
height: 100%;
grid-row: 3;
}
.banners {
height: 100%;
}
.banners > img {
width: 100%;
position: sticky;
}
.banner {
width: 100%;
}
.button,
:is(li, a, label)[type="button"],
input[type="submit"] {
padding: 10px 20px;
cursor: pointer;
text-align: center;
border: unset;
border-radius: 3px;
color: #fdfdfd;
background-color: #ad4717;
}
.button:hover,
:is(li, a, label)[type="button"]:hover,
input[type="submit"]:hover {
color: #fff;
background-color: #c5531f;
}
.button:active:is(:active, :focus),
:is(li, a, label)[type="button"]:is(:active, :focus),
input[type="radio"]:checked + label[type="button"],
input[type="submit"]:is(:active, :focus) {
color: #ddd;
background-color: #993f15;
}
input:is([type="checkbox"], [type="radio"]) {
display: none;
}
select,
input:is([type="text"], [type="password"]),
input:is([type="text"], [type="password"]).measured + .unit {
padding: 8px 12px;
outline: unset;
border-radius: 3px;
border: unset;
}
select {
padding-top: unset;
padding-bottom: unset;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
cursor: pointer;
}
select.measured,
input:is([type="text"], [type="password"]).measured {
margin-right: unset;
padding-right: 3px;
text-align: right;
border-radius: 3px 0 0 3px;
}
select.measured + .unit,
input:is([type="text"], [type="password"]).measured + .unit {
margin-right: 3px;
padding-left: unset;
display: inline;
border-radius: 0 3px 3px 0;
background-color: #fff;
}
.unit {
display: none;
}
.unselectable,
.unselectable * {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.unselectable::selection,
.unselectable *::-moz-selection {
background: none;
}
.divider {
width: 100%;
border-radius: 2px;
}
.divider + h3 {
text-align: center;
}
section > div.divider {
margin-bottom: 1rem;
border-bottom: 3px solid #aaa9a9;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1,4 @@
<svg width="45" height="45" viewBox="0 0 45 45" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.625 7.5H7.5C6.50544 7.5 5.55161 7.89509 4.84835 8.59835C4.14509 9.30161 3.75 10.2554 3.75 11.25V37.5C3.75 38.4946 4.14509 39.4484 4.84835 40.1516C5.55161 40.8549 6.50544 41.25 7.5 41.25H33.75C34.7446 41.25 35.6984 40.8549 36.4016 40.1516C37.1049 39.4484 37.5 38.4946 37.5 37.5V24.375" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M34.6875 4.6875C35.4334 3.94158 36.4451 3.52252 37.5 3.52252C38.5549 3.52252 39.5666 3.94158 40.3125 4.6875C41.0584 5.43342 41.4775 6.44511 41.4775 7.5C41.4775 8.55489 41.0584 9.56658 40.3125 10.3125L22.5 28.125L15 30L16.875 22.5L34.6875 4.6875Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

View File

@@ -0,0 +1,6 @@
<svg width="45" height="45" viewBox="0 0 45 45" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.625 11.25H9.375H39.375" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M35.625 11.25V37.5C35.625 38.4946 35.2299 39.4484 34.5266 40.1516C33.8234 40.8549 32.8696 41.25 31.875 41.25H13.125C12.1304 41.25 11.1766 40.8549 10.4733 40.1516C9.77009 39.4484 9.375 38.4946 9.375 37.5V11.25M15 11.25V7.5C15 6.50544 15.3951 5.55161 16.0984 4.84835C16.8016 4.14509 17.7554 3.75 18.75 3.75H26.25C27.2446 3.75 28.1984 4.14509 28.9016 4.84835C29.6049 5.55161 30 6.50544 30 7.5V11.25" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18.75 20.625V31.875" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M26.25 20.625V31.875" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 931 B

View File

@@ -8,6 +8,8 @@ namespace kodorvan\surikov;
use mirzaev\minimal\core, use mirzaev\minimal\core,
mirzaev\minimal\route; mirzaev\minimal\route;
error_reporting(E_ALL);
// Initializing path to the public directory // Initializing path to the public directory
define('INDEX', __DIR__); define('INDEX', __DIR__);
@@ -26,33 +28,41 @@ define('SETTINGS', INDEX . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 's
// Initializing the database connection parameters // Initializing the database connection parameters
define('DATABASE', require(SETTINGS . DIRECTORY_SEPARATOR . 'database.php')); 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 // Initializing dependencies
require ROOT . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; require ROOT . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
// Initializing the core // Initializing the core
$core = new core(namespace: __NAMESPACE__); $core = new core(
namespace: __NAMESPACE__
);
// Initializing routes // Initializing routes
$core->router $core->router
->write('/', new route('index', 'index'), 'GET') // ->write('/', new route('index', 'index'), 'GET')
->write('/', new route('publications', 'index'), 'GET')
->write('/publications/delete/$identifier', new route('publications', 'delete'), 'POST')
->write('/publications/write', new route('publications', 'write'), 'POST')
->write('/publications/update/$identifier', new route('publications', 'update'), 'POST')
->write('/storage/publications/$identifier', new route('publications', 'read'), 'GET')
->write('/account/registration', new route('accounts', 'registration'), 'POST') ->write('/account/registration', new route('accounts', 'registration'), 'POST')
->write('/account/authentication', new route('accounts', 'authentication'), 'POST') ->write('/account/authentication', new route('accounts', 'authentication'), 'POST')
->write('/account/deauthentication', new route('accounts', 'deauthentication'), 'POST') ->write('/account/deauthentication', new route('accounts', 'deauthentication'), 'POST')
->write('/account/deauthentication', new route('accounts', 'deauthentication'), 'GET') ->write('/account/deauthentication', new route('accounts', 'deauthentication'), 'GET')
->write('/books', new route('books', 'index'), 'GET') ->write('/books', new route('books', 'index'), 'GET')
->write('/books/$id', new route('books', 'index'), 'GET') ->write('/books/$identifier', new route('books', 'index'), 'GET')
->write('/books/$id/$page', new route('books', 'index'), 'GET') ->write('/books/$identifier/$page', new route('books', 'index'), 'GET')
->write('/books/$id/$page/rotate', new route('books', 'rotate'), 'POST') ->write('/books/$identifier/$page/rotate', new route('books', 'rotate'), 'POST')
->write('/books/$id/delete', new route('books', 'delete'), 'POST') ->write('/books/$identifier/delete', new route('books', 'delete'), 'POST')
->write('/storage/books/$id/$file', new route('books', 'read'), 'GET') ->write('/storage/books/$identifier/$page', new route('books', 'read'), 'GET')
->write('/storage/books/write', new route('books', 'write'), 'POST') ->write('/storage/books/write', new route('books', 'write'), 'POST')
->write('/kemenov', new route('kemenov', 'index'), 'GET') ->write('/kemenov', new route('kemenov', 'index'), 'GET')
->write('/surikov', new route('surikov', 'index'), 'GET') ->write('/surikov', new route('surikov', 'index'), 'GET')
->write('/contacts', new route('contacts', 'index'), 'GET'); ->write('/contacts', new route('contacts', 'index'), 'GET');
;
// Handling the request // Handling the request
$core->start(); $core->start();

View File

@@ -1,37 +1,64 @@
'use strict'; 'use strict';
/**
* Switches the lock state (open/closed) and updates the corresponding input field
*
* @param {HTMLElement} target - the HTML element that was clicked (the lock icon)
*
* @returns {void}
*/
function remember_switch(target) { function remember_switch(target) {
if (target.classList.contains('fa-unlock')) {
// Найден "открытый замок"
// Перезапись на "закрытый замок" if (target.classList.contains('fa-unlock')) {
// An "open lock" has been found
// Rewriting to "closed lock"
target.classList.remove('fa-unlock'); target.classList.remove('fa-unlock');
target.classList.add('fa-lock'); target.classList.add('fa-lock');
// Изменение отправляемого значения // Changing the value being sent
document.querySelector('input[name=' + target.getAttribute('for') + ']').checked = true; document.querySelector('input[name=' + target.getAttribute('for') + ']').checked = true;
} else { } else {
// Не найден "открытый замок", подразумевается, что найден "закрытый замок" // No "open lock" found, which means a "closed lock" was found
// Перезапись на "открытый замок" // Rewriting to "open lock"
target.classList.remove('fa-lock'); target.classList.remove('fa-lock');
target.classList.add('fa-unlock'); target.classList.add('fa-unlock');
// Изменение отправляемого значения // Changing the value being sent
document.querySelector('input[name=' + target.getAttribute('for') + ']').checked = false; document.querySelector('input[name=' + target.getAttribute('for') + ']').checked = false;
} }
} }
/**
* Handles the authentication form by setting the URL for submission
*
* @param {HTMLFormElement} form - The HTML form to be processed
*
* @returns {boolean} Always returns true (successful processing)
*/
function authentication(form) { function authentication(form) {
// Инициализация адреса отправки формы
// Initializing the form submission address
form.action = '/account/authentication'; form.action = '/account/authentication';
//Exit (succes)
return true; return true;
} }
/**
* Handles the registration form by setting the submission URL
*
* @param {HTMLFormElement} form - The HTML form to be processed
*
* @returns {boolean} Always returns true (successful processing)
*/
function registration(form) { function registration(form) {
// Инициализация адреса отправки формы
// Initializing the form submission address
form.action = '/account/registration'; form.action = '/account/registration';
//Exit (succes)
return true; return true;
} }

View File

@@ -0,0 +1,30 @@
'use strict';
/**
* Deletes a book by ID and the corresponding element from the DOM
*
* @param {number} id - The ID of the book to delete
*/
function trash(id, element) {
// Sending a POST request to the server
fetch(`books/${id}/delete`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'identifier=' + id
})
// Checking the success of the request
.then((response) => {
if (response.ok) {
// Removing an element from the DOM
element.remove();
}
})
.catch((error) => {
console.error('Ошибка:', error);
});
}

View File

@@ -0,0 +1,27 @@
'use strict';
/**
* Rotates the page of a book and reloads the page if necessary
*
* @param {number} identifier - Book ID
* @param {number} page - Page number to rotate
* @param {boolean} [reload=false] - Whether to reload the page after rotation
*
* @returns {void}
*/
function rotate(id, page, reload = false) {
if (typeof id === 'number' && typeof page === 'number') {
// Получены входные параметры
// Запрос
fetch(`/books/${id}/${page}/rotate`, {
method: 'POST'
}).then((response) => response.text()
).then(
(response) => {
location.reload();
}
);
}
}

View File

@@ -0,0 +1,4 @@
function turning() {
const image = document.getElementById('page'); image.style.rotate = ((isNaN(buffer = parseInt(image.style.rotate)) ? 0 : buffer) + 90) + 'deg'; (((isNaN(buffer) ? 0 : buffer) / 90) % 2) === 0 ? (image.style.margin = '15% 0px 20% 0px', image.style.width = '100%') : image.style.margin = null
}

View File

@@ -1,25 +0,0 @@
'use strict';
function remove(id, element = null) {
if (typeof id === 'number') {
// Получены входные параметры
// Запрос
fetch(`https://surikovlib.ru/books/${id}/delete`, {
method: 'POST'
}).then(
(value) => {
return value.json();
}
).then(
(response) => {
if (response.status === true) {
// Удалена книга
// Удаление элемента
if (typeof element === 'object') element.remove();
}
}
);
}
}

View File

@@ -0,0 +1,78 @@
'use strict';
//Hash of the last post
let lastPublications = '';
/**
* Sends the post form data to the server and adds a new post to the page
*/
function create() {
//FormData object for sending form data
const formData = new FormData();
//Form elements
const fileInput = document.querySelector('#file-input');
const titleInput = document.getElementById('title');
const contentInput = document.getElementById('content');
// Get values and trim spaces
const title = titleInput.value.trim();
const content = contentInput.value.trim();
// Checking for empty fields
if (!title) {
alert('Заголовок не может быть пустыми!');
return;
}
let hash = title + content;
// Checking the hash of the previous publication
if (hash === lastPublications) {
//Form data reset
titleInput.value = '';
contentInput.value = '';
fileInput.value = '';
return;
}
//Getting data from FormData
formData.append('title', titleInput.value);
formData.append('content', contentInput.value);
//Adding files
if (fileInput.files.length != 0) {
for (let i = 0; i < fileInput.files.length; i++) {
formData.append('publications[]', fileInput.files[i]);
}
}
fetch('/publications/write', {
method: 'POST',
body: formData
})
.then((response) => response.text())
.then((text) => {
//Saving the hash (success)
lastPublications = hash;
hash = '';
//Clearing form fields (success)
titleInput.value = '';
contentInput.value = '';
fileInput.value = '';
//Creating an html publication
const publication = document.createElement('div');
const publications = document.getElementById('publications');
publications.insertBefore(publication, publications.firstElementChild);
publication.outerHTML = text;
})
.catch((error) => {
console.error('Error:', error);
});
}

View File

@@ -0,0 +1,28 @@
'use strict';
/**
* Updates the content of a publication by ID
*
* @param {int} publicationId - Publication ID to update
*/
function trash(id, element) {
// Sending a POST request to the server
fetch('/publications/delete/' + id, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'identifier=' + id
})
.then((response) => {
if (response.ok) {
// Removing an element from the DOM
element.remove();
}
})
.catch((error) => {
console.error('Ошибка:', error);
});
}

View File

@@ -0,0 +1,100 @@
'use strict';
/**
* Updates the content of a publication by ID
*
* @param {int} publicationId - Publication ID to update
*/
function update(publicationId) {
// Getting the DOM elements of the post's title and content, image
const publicationTitle = document.getElementById('publication-title-' + publicationId)
const publicationContent = document.getElementById('publication-content-' + publicationId)
const publicationImage = document.getElementById('img-' + publicationId)
// Requesting a new title from the user
let title = prompt('Введите название для публикации', publicationTitle.textContent.trim())
//Checking that the title is not empty
if (!title.trim()) {
alert('Заголовок не может быть пустыми!');
return;
} else {
// Requesting a new content from the user
let content = prompt('Введите содержание для публикации', publicationContent.textContent.trim())
let image = confirm('Изменить изображение?');
if (image) {
//Creating an input to select a file
const input = document.createElement('input');
//Configuring input
input.type = 'file'
input.id = 'image'
input.accept = '.jpg,.jpeg,.png,.gif'
input.name = 'publications[]'
input.style.display = 'none'
document.body.appendChild(input);
input.addEventListener('change', function () {
//If the image is uploaded
send(publicationId, title, content, input.files, publicationTitle, publicationContent, publicationImage)
document.body.removeChild(input);
})
//Click simulation
input.click();
} else {
//If the image is not uploaded
send(publicationId, title, content, null, publicationTitle, publicationContent, null)
}
}
}
/**
* Sends the updated publication data to the server
*
* @param {int} publicationId - Publication ID
* @param {string} title - Title
* @param {string} content - Content
* @param {FileList|null} files - List of files to upload
* @param {HTMLElement} publicationTitle - DOM element of the title
* @param {HTMLElement} publicationContent - DOM element of the content
* @param {HTMLElement|null} publicationImage - DOM-element of the image
*/
function send(publicationId, title, content, files, publicationTitle, publicationContent, publicationImage) {
//The FormData object for sending form data
const formData = new FormData();
//Adding the publication data to FormData
formData.append('identifier', publicationId);
formData.append('title', title);
formData.append('content', content);
// If there are files to upload, add them to FormData
if (files != null) {
for (let file of files) {
formData.append('publications[]', file)
}
}
fetch('/publications/update/' + publicationId,
{
method: 'POST',
body: formData
})
.then((response) => response.text())
.then((text) => {
// Updating publication data in DOM
publicationTitle.textContent = title;
publicationContent.textContent = content;
publicationImage.src = "storage/publication/" + publicationId;
})
.catch((error) => {
console.error('Error:', error);
});
}

View File

@@ -1,24 +0,0 @@
'use strict';
function rotate(id, page, reload = false) {
if (typeof id === 'number' && typeof page === 'number') {
// Получены входные параметры
// Запрос
fetch(`https://surikovlib.ru/books/${id}/${page}/rotate`, {
method: 'POST'
}).then(
(value) => {
return value.json();
}
).then(
(response) => {
if (response.status === true) {
// Перевёрнута страница
if (reload === true) location.reload();
}
}
);
}
}

View File

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

View File

@@ -4,7 +4,7 @@
return [ return [
'type' => 'mysql', 'type' => 'mysql',
'host' => '127.0.0.1', 'host' => '127.0.0.1',
'name' => 'casino_2', 'name' => 'youtube_parser',
'login' => 'brawl_stars', 'login' => 'roma',
'password' => 'knopka_bablo_228' 'password' => 'America_pizda128'
]; ];

View File

@@ -1,4 +1,8 @@
/books/* /books/*
/temp/* /temp/*
/images/*
/publications/*
!/books/.gitkeep !/books/.gitkeep
!/temp/.gitkeep !/temp/.gitkeep
!/images/.gitkeep
!/publications/.gitkeep

View File

@@ -65,7 +65,7 @@ final class templater extends controller implements array_access
$this->twig = new twig(new FilesystemLoader(VIEWS)); $this->twig = new twig(new FilesystemLoader(VIEWS));
// Initializing global variables // Initializing global variables
$this->twig->addGlobal('theme', 'default'); $this->twig->addGlobal('theme', 'test');
$this->twig->addGlobal('server', $_SERVER); $this->twig->addGlobal('server', $_SERVER);
$this->twig->addGlobal('cookies', $_COOKIE); $this->twig->addGlobal('cookies', $_COOKIE);
if ($account instanceof account) $this->twig->addGlobal('account', $account); if ($account instanceof account) $this->twig->addGlobal('account', $account);

View File

@@ -5,8 +5,6 @@
<head> <head>
{% include '/themes/default/head.html' %} {% include '/themes/default/head.html' %}
<link href="/css/main.css" rel="stylesheet">
<title> <title>
{% block title %} {% block title %}
Библиотека Сурикова Библиотека Сурикова
@@ -19,9 +17,10 @@
<aside> <aside>
{% include '/themes/default/sidebar.html' %} {% include '/themes/default/sidebar.html' %}
</aside><!----><main> </aside><!---->
<main>
{% block main %} {% block main %}
{% include '/themes/default/calculators/index.html' %} <!-- {% include '/themes/default/calculators/index.html' %} -->
{% endblock %} {% endblock %}
</main> </main>

View File

@@ -1,2 +1,8 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link href="/css/main.css" riel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Geologica:wght@100..900&display=swap" rel="stylesheet">

View File

@@ -6,28 +6,33 @@
<section id="page"> <section id="page">
<article> <article>
<h2>Суриков</h2> <h2>Суриков</h2>
<p>Библиотека научно-вспомогательного фонда <b>Музея-усадьбы В.И. Сурикова</b> начала формироваться с 1948 года, когда <p>Библиотека научно-вспомогательного фонда <b>Музея-усадьбы В.И. Сурикова</b> начала формироваться с 1948 года,
когда
первые сотрудники самостоятельно стали собирать её, приобретая за свои средства необходимую для работы первые сотрудники самостоятельно стали собирать её, приобретая за свои средства необходимую для работы
литературу, прежде всего по осмыслению жизни и творчества <b>В.И. Сурикова</b>, а также специальную литературу, прежде всего по осмыслению жизни и творчества <b>В.И. Сурикова</b>, а также специальную
профессиональную литературу по экспонированию и иной деятельности в музее. В это время было начато системное профессиональную литературу по экспонированию и иной деятельности в музее. В это время было начато системное
взаимодействие с <b>Центральной библиотечной системой им. Горького</b>, поэтому многие издания, поступавшие из взаимодействие с <b>Центральной библиотечной системой им. Горького</b>, поэтому многие издания, поступавшие
из
библиотеки в музей, имеют её опознавательный знак. библиотеки в музей, имеют её опознавательный знак.
Позже, уже с приходом Л. П. Греченко, директора музея-усадьбы с 1970 по 2008 гг., было принято решение Позже, уже с приходом Л. П. Греченко, директора музея-усадьбы с 1970 по 2008 гг., было принято решение
ежемесячно закупать книги из бибколлектора (ныне «Центр книги Красноярский бибколлектор»). </p> ежемесячно закупать книги из бибколлектора (ныне «Центр книги Красноярский бибколлектор»). </p>
<p>Но в 1990-е годы в период экономического спада и дефицита закупка прекратилась, с этого времени библиотека <p>Но в 1990-е годы в период экономического спада и дефицита закупка прекратилась, с этого времени библиотека
продолжила формироваться за счёт <b>личного вклада</b> сотрудников музея. Особое значение библиотеке уделяла <b>Нина продолжила формироваться за счёт <b>личного вклада</b> сотрудников музея. Особое значение библиотеке уделяла
<b>Нина
Ярославовна Скалиш</b>, главный хранитель музея на протяжении 45 лет. Ярославовна Скалиш</b>, главный хранитель музея на протяжении 45 лет.
Основой библиотеки является научная литература, монографии о жизни и творчестве В. И. Сурикова, изданные с Основой библиотеки является научная литература, монографии о жизни и творчестве В. И. Сурикова, изданные с
1914 по 2018 гг. (кроме того, статьи, каталоги и художественная литература). Библиотека музея включает 1914 по 2018 гг. (кроме того, статьи, каталоги и художественная литература). Библиотека музея включает
архивный фонд В. С. Кеменова: рукописи, документы и фотографии, собранные известным суриковедом. Его архивный фонд В. С. Кеменова: рукописи, документы и фотографии, собранные известным суриковедом. Его
исследования, не вошедшие в первый том «Историческая живопись В.И. Сурикова», являются фундаментальными и исследования, не вошедшие в первый том «Историческая живопись В.И. Сурикова», являются фундаментальными и
охватывают период с 1890 по 1916 годы жизни В. И. Сурикова. </p> охватывают период с 1890 по 1916 годы жизни В. И. Сурикова. </p>
<p>Стоит отметить тот факт, что музейная библиотека является кладезью <b>настоящих библиографических редкостей</b>, к <p>Стоит отметить тот факт, что музейная библиотека является кладезью <b>настоящих библиографических
редкостей</b>, к
которым можно отнести дореволюционные издания о великом русском художнике, книги с дарственной надписью его которым можно отнести дореволюционные издания о великом русском художнике, книги с дарственной надписью его
потомков; а также книги и периодику из личного собрания В. М. Крутовского с его подписью; и многочисленные потомков; а также книги и периодику из личного собрания В. М. Крутовского с его подписью; и многочисленные
дореволюционные нотные издания, оставшиеся в доме художника от семьи музыкантов, квартирантов Красиковых. дореволюционные нотные издания, оставшиеся в доме художника от семьи музыкантов, квартирантов Красиковых.
Таким образом, <b>общее количество всех изданий по В.И. Сурикову, собранных в библиотеке музея, составляет Таким образом, <b>общее количество всех изданий по В.И. Сурикову, собранных в библиотеке музея, составляет
более 100</b>. Сейчас музейная библиография публикуется впервые, ведь следуя миссии музея мы делаем открытым более 100</b>. Сейчас музейная библиография публикуется впервые, ведь следуя миссии музея мы делаем
открытым
доступ к тем книжным ценностям, которые помогут будущим исследователям в изучении творческого наследия В. И. доступ к тем книжным ценностям, которые помогут будущим исследователям в изучении творческого наследия В. И.
Сурикова.</p> Сурикова.</p>
</article> </article>

View File

@@ -6,28 +6,33 @@
<section id="page"> <section id="page">
<article> <article>
<h2>Суриков</h2> <h2>Суриков</h2>
<p>Библиотека научно-вспомогательного фонда <b>Музея-усадьбы В.И. Сурикова</b> начала формироваться с 1948 года, когда <p>Библиотека научно-вспомогательного фонда <b>Музея-усадьбы В.И. Сурикова</b> начала формироваться с 1948 года,
когда
первые сотрудники самостоятельно стали собирать её, приобретая за свои средства необходимую для работы первые сотрудники самостоятельно стали собирать её, приобретая за свои средства необходимую для работы
литературу, прежде всего по осмыслению жизни и творчества <b>В.И. Сурикова</b>, а также специальную литературу, прежде всего по осмыслению жизни и творчества <b>В.И. Сурикова</b>, а также специальную
профессиональную литературу по экспонированию и иной деятельности в музее. В это время было начато системное профессиональную литературу по экспонированию и иной деятельности в музее. В это время было начато системное
взаимодействие с <b>Центральной библиотечной системой им. Горького</b>, поэтому многие издания, поступавшие из взаимодействие с <b>Центральной библиотечной системой им. Горького</b>, поэтому многие издания, поступавшие
из
библиотеки в музей, имеют её опознавательный знак. библиотеки в музей, имеют её опознавательный знак.
Позже, уже с приходом Л. П. Греченко, директора музея-усадьбы с 1970 по 2008 гг., было принято решение Позже, уже с приходом Л. П. Греченко, директора музея-усадьбы с 1970 по 2008 гг., было принято решение
ежемесячно закупать книги из бибколлектора (ныне «Центр книги Красноярский бибколлектор»). </p> ежемесячно закупать книги из бибколлектора (ныне «Центр книги Красноярский бибколлектор»). </p>
<p>Но в 1990-е годы в период экономического спада и дефицита закупка прекратилась, с этого времени библиотека <p>Но в 1990-е годы в период экономического спада и дефицита закупка прекратилась, с этого времени библиотека
продолжила формироваться за счёт <b>личного вклада</b> сотрудников музея. Особое значение библиотеке уделяла <b>Нина продолжила формироваться за счёт <b>личного вклада</b> сотрудников музея. Особое значение библиотеке уделяла
<b>Нина
Ярославовна Скалиш</b>, главный хранитель музея на протяжении 45 лет. Ярославовна Скалиш</b>, главный хранитель музея на протяжении 45 лет.
Основой библиотеки является научная литература, монографии о жизни и творчестве В. И. Сурикова, изданные с Основой библиотеки является научная литература, монографии о жизни и творчестве В. И. Сурикова, изданные с
1914 по 2018 гг. (кроме того, статьи, каталоги и художественная литература). Библиотека музея включает 1914 по 2018 гг. (кроме того, статьи, каталоги и художественная литература). Библиотека музея включает
архивный фонд В. С. Кеменова: рукописи, документы и фотографии, собранные известным суриковедом. Его архивный фонд В. С. Кеменова: рукописи, документы и фотографии, собранные известным суриковедом. Его
исследования, не вошедшие в первый том «Историческая живопись В.И. Сурикова», являются фундаментальными и исследования, не вошедшие в первый том «Историческая живопись В.И. Сурикова», являются фундаментальными и
охватывают период с 1890 по 1916 годы жизни В. И. Сурикова. </p> охватывают период с 1890 по 1916 годы жизни В. И. Сурикова. </p>
<p>Стоит отметить тот факт, что музейная библиотека является кладезью <b>настоящих библиографических редкостей</b>, к <p>Стоит отметить тот факт, что музейная библиотека является кладезью <b>настоящих библиографических
редкостей</b>, к
которым можно отнести дореволюционные издания о великом русском художнике, книги с дарственной надписью его которым можно отнести дореволюционные издания о великом русском художнике, книги с дарственной надписью его
потомков; а также книги и периодику из личного собрания В. М. Крутовского с его подписью; и многочисленные потомков; а также книги и периодику из личного собрания В. М. Крутовского с его подписью; и многочисленные
дореволюционные нотные издания, оставшиеся в доме художника от семьи музыкантов, квартирантов Красиковых. дореволюционные нотные издания, оставшиеся в доме художника от семьи музыкантов, квартирантов Красиковых.
Таким образом, <b>общее количество всех изданий по В.И. Сурикову, собранных в библиотеке музея, составляет Таким образом, <b>общее количество всех изданий по В.И. Сурикову, собранных в библиотеке музея, составляет
более 100</b>. Сейчас музейная библиография публикуется впервые, ведь следуя миссии музея мы делаем открытым более 100</b>. Сейчас музейная библиография публикуется впервые, ведь следуя миссии музея мы делаем
открытым
доступ к тем книжным ценностям, которые помогут будущим исследователям в изучении творческого наследия В. И. доступ к тем книжным ценностям, которые помогут будущим исследователям в изучении творческого наследия В. И.
Сурикова.</p> Сурикова.</p>
</article> </article>

View File

@@ -0,0 +1,39 @@
<section id="authentication">
<link href="/css/auth.css" rel="stylesheet">
{% if account is not empty %}
<h3 class="unselectable">Аккаунт</h3>
<div id="account">
<p><b class="unselectable"> Почта:</b><span title="{{ account.mail }}"> {{ account.mail
}}</span></p>
<a class="exit unselectable" type="button" href='/account/deauthentication'>Выход</a>
</div>
{% else %}
<h3 class="unselectable">Аутентификация</h3>
<form method="POST" accept-charset="UTF-8">
<input type="text" name="mail" placeholder="Почта">
<input type="password" name="password" placeholder="Пароль">
<div class="submit">
<label class="button unselectable fas fa-unlock" for="remember"
onclick="return remember_switch(this);"></label>
<input type="checkbox" name="remember" value="1">
<input type="submit" value="Войти" onclick="return authentication(this.parentElement.parentElement);">
</div>
<input type="submit" class="registration" value="Зарегистрироваться"
onclick="return registration(this.parentElement);">
{% if errors is not empty %}
{% if errors.account is not empty %}
<ul class="errors">
{% for error in errors.account %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{% endif %}
</form>
{% endif %}
<script type="text/javascript" src="/js/auth.js" defer></script>
</section>

View File

@@ -0,0 +1,47 @@
{% extends "/themes/test/core.html" %}
{% block main %}
<link href="/css/books.css" rel="stylesheet">
<section id="book">
<h2>{{ book.title|e }}</h2>
<img id="page" class="unselectable" src="/storage/books/{{ book.identifier|e }}/{{ page|e }}"
alt='Страница отсутствует'>
<nav>
<ul>
{% if page != last %}
<!-- <li class="previous unselectable" type="button"><a href="/books/{{ book.identifier|e }}/{{ last|e }}"
title="Страница №{{ last|e }}">Назад</a></li> -->
<li class="previous unselectable">
<a href="/books/{{ book.identifier|e }}/{{ last|e }}" title="Страница №{{ last|e }}"><i
class="icon arrow" style="--rotate: 180deg; color:#9b3d10"></i></a>
</li>
{% endif %}
{% if account.permissions.books is defined and account.permissions.books == 1 %}
<li class="middle icon unselectable"><i class="fa-solid fa-rotate-right unselectable" type="button"
onclick="rotate({{ book.identifier|e }}, {{ page|e }}, true)" title="Повернуть страницу"></i></li>
<script type="text/javascript" src="/js/book/rotate.js"></script>
{% else %}
<li class="middle icon unselectable"><i class="fa-solid fa-rotate-right unselectable" type="button"
onclick="turning()" title="Повернуть страницу"></i></li>
<script type="text/javascript" src="/js/book/turning.js"></script>
{% endif %}
{%if page != next %}
<li class="next unselectable"><a href="/books/{{ book.identifier|e }}/{{ next|e }}"
title="Страница №{{ next|e }}"><i class="icon arrow" style="--rotate: 0deg;color:#9b3d10"></i></a>
</li>
{%endif%}
</ul>
</nav>
</section>
{% endblock %}

View File

@@ -0,0 +1,39 @@
{% extends "/themes/test/core.html" %}
{% block main %}
<link href="/css/books.css" rel="stylesheet">
<link href="/css/upload.css" rel="stylesheet">
<section id="books">
{% if account is not empty %}
{% if account.permissions.books is defined and account.permissions.books == 1 %}
<form class="upload unselectable" action="/storage/books/write" enctype="multipart/form-data" method="POST">
<input type="file" name="books[]" accept=".pdf" oninput="this.parentElement.submit();" multiple="true">
<p>+</p>
</form>
{% endif %}
{% endif %}
{% for book in books %}
<article class="book">
{% if account.permissions.books is defined and account.permissions.books == 1 %}
<button onclick="return remove({{ book.id|e }}, this.parentElement);" title="Удалить"><i
class="fa-solid fa-xmark"></i></button>
{% endif %}
<img src="/storage/books/{{ book.id|e }}/0" class="image-book unselectable" alt='Обложка книги "{{ book.title|e }}"'>
<h4><a href="/books/{{ book.id|e }}">{{ book.title|e }}</a></h3>
</article>
{% endfor %}
{% if account.permissions.books is defined and account.permissions.books == 1 %}
<script type="text/javascript" src="/js/book/delete.js"></script>
{% endif %}
</section>
{% endblock %}

View File

@@ -0,0 +1,42 @@
{% extends "/themes/test/core.html" %}
{% block main %}
<link href="/css/books.css" rel="stylesheet">
<link href="/css/upload.css" rel="stylesheet">
<section id="books">
{% if account is not empty %}
{% if account.permissions.books is defined and account.permissions.books == 1 %}
<form class="upload unselectable" action="/storage/books/write" enctype="multipart/form-data" method="POST">
<input type="file" name="books[]" accept=".pdf" oninput="this.parentElement.submit();" multiple="true">
<p>+</p>
</form>
{% endif %}
{% endif %}
{% for book in books %}
{% if book.active == 1 %}
<article class="book" id="book-{{ book.identifier|e }}">
{% if account.permissions.books is defined and account.permissions.books == 1 %}
<button onclick="trash({{ book.identifier|e }}, this.closest('.book'))" title="Удалить"><i
class="fa-solid fa-xmark"></i></button>
{% endif %}
<img src="/storage/books/{{ book.identifier|e }}/0" class="unselectable"
alt='Обложка книги "{{ book.title|e }}"'>
<h4><a href="/books/{{ book.identifier|e }}">{{ book.title|e }}</a></h3>
</article>
{%endif%}
{% endfor %}
{% if account.permissions.books is defined and account.permissions.books == 1 %}
<script type="text/javascript" src="/js/book/delete.js"></script>
{% endif %}
</section>
{% endblock %}

View File

@@ -0,0 +1,40 @@
<!doctype html>
<html lang="ru">
<head>
{% include '/themes/test/head.html' %}
<link href="/css/test/test-main.css" rel="stylesheet">
<link href="/css/publications.css" rel="stylesheet">
<link href="/css/icons/arrow.css" rel="stylesheet">
<title>
{% block title %}
Библиотека Сурикова
{% endblock %}
</title>
</head>
<body>
{% include '/themes/test/header.html' %}
<aside>
{% include '/themes/test/sidebar.html' %}
</aside>
<main>
{% block main %}
{% endblock %}
</main>
{% include '/themes/test/footer.html' %}
</body>
{% include '/themes/test/js.html' %}
</body>
</html>

View File

@@ -0,0 +1,3 @@
<footer>
</footer>

View File

@@ -0,0 +1,8 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Geologica:wght@100..900&display=swap" rel="stylesheet">

View File

@@ -0,0 +1,18 @@
<header class="unselectable">
<section class="menu">
<nav>
<a class="link" href="/kemenov" title="Архивный фонд">Кеменов</a>
<a class="link" href="/surikov" title="Список книг">Суриков</a>
<a id="logo" href="/" title="Главная страница">
<img src="/img/surikovlib_logo_1_white.svg">
</a>
<a class="link" href="/books" title="Читать книги">Библиотека</a>
<a class="link" href="/contacts" title="Контакты администрации">Контакты</a>
</nav>
</section>
<section class="window">
<img class="img_1" src="/img/ФЖ-28.png" alt="">
<img class="background" src="/img/background_1.png" alt="">
</section>
</header>

View File

@@ -0,0 +1,8 @@
<script type="text/javascript" src="/js/auth.js"></script>
<script src="https://kit.fontawesome.com/d67f03b1ae.js" crossorigin="anonymous"></script>
{% block js %}
{% endblock %}

View File

@@ -0,0 +1,6 @@
{% extends "/themes/test/core.html" %}
{% block main %}
{% include '/themes/test/publications.html' %}
{% endblock %}

View File

@@ -0,0 +1,40 @@
{% extends "/themes/test/core.html" %}
{% block main %}
<link href="/css/pages.css" rel="stylesheet">
<section id="page">
<article>
<h2>Суриков</h2>
<p>Библиотека научно-вспомогательного фонда <b>Музея-усадьбы В.И. Сурикова</b> начала формироваться с 1948 года,
когда
первые сотрудники самостоятельно стали собирать её, приобретая за свои средства необходимую для работы
литературу, прежде всего по осмыслению жизни и творчества <b>В.И. Сурикова</b>, а также специальную
профессиональную литературу по экспонированию и иной деятельности в музее. В это время было начато системное
взаимодействие с <b>Центральной библиотечной системой им. Горького</b>, поэтому многие издания, поступавшие
из
библиотеки в музей, имеют её опознавательный знак.
Позже, уже с приходом Л. П. Греченко, директора музея-усадьбы с 1970 по 2008 гг., было принято решение
ежемесячно закупать книги из бибколлектора (ныне «Центр книги Красноярский бибколлектор»). </p>
<p>Но в 1990-е годы в период экономического спада и дефицита закупка прекратилась, с этого времени библиотека
продолжила формироваться за счёт <b>личного вклада</b> сотрудников музея. Особое значение библиотеке уделяла
<b>Нина
Ярославовна Скалиш</b>, главный хранитель музея на протяжении 45 лет.
Основой библиотеки является научная литература, монографии о жизни и творчестве В. И. Сурикова, изданные с
1914 по 2018 гг. (кроме того, статьи, каталоги и художественная литература). Библиотека музея включает
архивный фонд В. С. Кеменова: рукописи, документы и фотографии, собранные известным суриковедом. Его
исследования, не вошедшие в первый том «Историческая живопись В.И. Сурикова», являются фундаментальными и
охватывают период с 1890 по 1916 годы жизни В. И. Сурикова. </p>
<p>Стоит отметить тот факт, что музейная библиотека является кладезью <b>настоящих библиографических
редкостей</b>, к
которым можно отнести дореволюционные издания о великом русском художнике, книги с дарственной надписью его
потомков; а также книги и периодику из личного собрания В. М. Крутовского с его подписью; и многочисленные
дореволюционные нотные издания, оставшиеся в доме художника от семьи музыкантов, квартирантов Красиковых.
Таким образом, <b>общее количество всех изданий по В.И. Сурикову, собранных в библиотеке музея, составляет
более 100</b>. Сейчас музейная библиография публикуется впервые, ведь следуя миссии музея мы делаем
открытым
доступ к тем книжным ценностям, которые помогут будущим исследователям в изучении творческого наследия В. И.
Сурикова.</p>
</article>
</section>
{% endblock %}

View File

@@ -0,0 +1,23 @@
{% extends "/themes/test/core.html" %}
{% block main %}
<link href="/css/pages.css" rel="stylesheet">
<section id="page">
<article>
<h2>Кеменов</h2>
<p>Научная деятельность <b>Владимира Семеновича Кеменова</b> (1908 1988), многолетнего вице-президента
Академии художеств СССР, составила целую эпоху в советском искусствоведении. Вклад ученого в его развитие
высоко оценен специалистами. Интересы В. С. Кеменова простирались «От Леонардо да Винчи до Рокуэлла Кента»
(так назывался его последний сборник статей) и охватывали западноевропейское (XV-XVII вв.), русское и
советское (XVIII-XX вв.), современное зарубежное искусство, теорию эстетики.</p>
<p>Однако центральное место в его творчестве занимал В. И. Суриков, о котором учёный писал с 1930-х гг. до конца
жизни. Неудивительно, что в 1989 г. вдова учёного Л. Г. Крамаренко передала большую часть архива В. С.
Кеменова <b>Музею-усадьбе В. И. Сурикова в Красноярске</b>. Сотрудники музея перевезли документы из Москвы,
разместили в административном здании (флигель усадьбы) и бережно сохранили в отдельном шкафу. В 2011 2012
гг. кандидат исторических наук И. А. Черкасов провёл разбор и описание архива, сформировал <b>личный
фонд</b> В. С. Кеменова (Ф. 1. Оп. 1-2. 81 ед. хр.), многие документы которого <b>до сих пор не введены
в научный оборот</b>.</p>
</article>
</section>
{% endblock %}

View File

@@ -0,0 +1,40 @@
{% extends "/themes/test/core.html" %}
{% block main %}
<link href="/css/pages.css" rel="stylesheet">
<section id="page">
<article>
<h2>Суриков</h2>
<p>Библиотека научно-вспомогательного фонда <b>Музея-усадьбы В.И. Сурикова</b> начала формироваться с 1948 года,
когда
первые сотрудники самостоятельно стали собирать её, приобретая за свои средства необходимую для работы
литературу, прежде всего по осмыслению жизни и творчества <b>В.И. Сурикова</b>, а также специальную
профессиональную литературу по экспонированию и иной деятельности в музее. В это время было начато системное
взаимодействие с <b>Центральной библиотечной системой им. Горького</b>, поэтому многие издания, поступавшие
из
библиотеки в музей, имеют её опознавательный знак.
Позже, уже с приходом Л. П. Греченко, директора музея-усадьбы с 1970 по 2008 гг., было принято решение
ежемесячно закупать книги из бибколлектора (ныне «Центр книги Красноярский бибколлектор»). </p>
<p>Но в 1990-е годы в период экономического спада и дефицита закупка прекратилась, с этого времени библиотека
продолжила формироваться за счёт <b>личного вклада</b> сотрудников музея. Особое значение библиотеке уделяла
<b>Нина
Ярославовна Скалиш</b>, главный хранитель музея на протяжении 45 лет.
Основой библиотеки является научная литература, монографии о жизни и творчестве В. И. Сурикова, изданные с
1914 по 2018 гг. (кроме того, статьи, каталоги и художественная литература). Библиотека музея включает
архивный фонд В. С. Кеменова: рукописи, документы и фотографии, собранные известным суриковедом. Его
исследования, не вошедшие в первый том «Историческая живопись В.И. Сурикова», являются фундаментальными и
охватывают период с 1890 по 1916 годы жизни В. И. Сурикова. </p>
<p>Стоит отметить тот факт, что музейная библиотека является кладезью <b>настоящих библиографических
редкостей</b>, к
которым можно отнести дореволюционные издания о великом русском художнике, книги с дарственной надписью его
потомков; а также книги и периодику из личного собрания В. М. Крутовского с его подписью; и многочисленные
дореволюционные нотные издания, оставшиеся в доме художника от семьи музыкантов, квартирантов Красиковых.
Таким образом, <b>общее количество всех изданий по В.И. Сурикову, собранных в библиотеке музея, составляет
более 100</b>. Сейчас музейная библиография публикуется впервые, ведь следуя миссии музея мы делаем
открытым
доступ к тем книжным ценностям, которые помогут будущим исследователям в изучении творческого наследия В. И.
Сурикова.</p>
</article>
</section>
{% endblock %}

View File

@@ -0,0 +1,47 @@
<div class="block-new" id="publication-{{publication.identifier|e }}">
<div class="new-inside">
<div class=" new-title" id="{{publication.identifier|e }}">
<h1 id="publication-title-{{publication.identifier|e }}">
{{publication.title|e }} </h1>
<div class="right-block">
{% if account.permissions.publications is defined and account.permissions.publications == 1 %}
<div class="new-btns">
<button id="edit" onclick="update({{publication.identifier|e}})">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M21.2635 2.29289C20.873 1.90237 20.2398 1.90237 19.8493 2.29289L18.9769 3.16525C17.8618 2.63254 16.4857 2.82801 15.5621 3.75165L4.95549 14.3582L10.6123 20.0151L21.2189 9.4085C22.1426 8.48486 22.338 7.1088 21.8053 5.99367L22.6777 5.12132C23.0682 4.7308 23.0682 4.09763 22.6777 3.70711L21.2635 2.29289ZM16.9955 10.8035L10.6123 17.1867L7.78392 14.3582L14.1671 7.9751L16.9955 10.8035ZM18.8138 8.98525L19.8047 7.99429C20.1953 7.60376 20.1953 6.9706 19.8047 6.58007L18.3905 5.16586C18 4.77534 17.3668 4.77534 16.9763 5.16586L15.9853 6.15683L18.8138 8.98525Z"
fill="currentColor" />
<path d="M2 22.9502L4.12171 15.1717L9.77817 20.8289L2 22.9502Z" fill="currentColor" />
</svg>
</button>
<button id="trash" onclick="trash({{publication.identifier|e}}, this.closest('.block-new'))">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M17 5V4C17 2.89543 16.1046 2 15 2H9C7.89543 2 7 2.89543 7 4V5H4C3.44772 5 3 5.44772 3 6C3 6.55228 3.44772 7 4 7H5V18C5 19.6569 6.34315 21 8 21H16C17.6569 21 19 19.6569 19 18V7H20C20.5523 7 21 6.55228 21 6C21 5.44772 20.5523 5 20 5H17ZM15 4H9V5H15V4ZM17 7H7V18C7 18.5523 7.44772 19 8 19H16C16.5523 19 17 18.5523 17 18V7Z"
fill="currentColor" />
<path d="M9 9H11V17H9V9Z" fill="currentColor" />
<path d="M13 9H15V17H13V9Z" fill="currentColor" />
</svg>
</button>
</div>
{%endif%}
<span>{{publication.created|date("d.m.Y") }}</span>
</div>
</div>
<p id="publication-content-{{publication.identifier|e }}">{{publication.content }}</p>
<img src="/storage/publications/{{publication.identifier|e }}" alt="Не удалось загрузить изображение" id="img-{{publication.identifier|e }}">
</div>
</div>

View File

@@ -0,0 +1,62 @@
<div class="content-main">
{% if account is not empty %}
{% if account.permissions.publications is defined and account.permissions.publications == 1 %}
<div class="add-new unselectable">
<input type="text" placeholder="Заголовок" name="title" id="title">
<textarea type="text" placeholder="Напишите что-нибудь.." name="content" id="content"></textarea>
<div class="new-btn">
<div class="custom-file-upload">
<input type="file" id="file-input" class="file-input" name="publications[]"
accept=".png,.gif,.jpeg,.jpg">
<label for="file-input" class="custom-file-button">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M7 7C5.34315 7 4 8.34315 4 10C4 11.6569 5.34315 13 7 13C8.65685 13 10 11.6569 10 10C10 8.34315 8.65685 7 7 7ZM6 10C6 9.44772 6.44772 9 7 9C7.55228 9 8 9.44772 8 10C8 10.5523 7.55228 11 7 11C6.44772 11 6 10.5523 6 10Z"
fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M3 3C1.34315 3 0 4.34315 0 6V18C0 19.6569 1.34315 21 3 21H21C22.6569 21 24 19.6569 24 18V6C24 4.34315 22.6569 3 21 3H3ZM21 5H3C2.44772 5 2 5.44772 2 6V18C2 18.5523 2.44772 19 3 19H7.31374L14.1924 12.1214C15.364 10.9498 17.2635 10.9498 18.435 12.1214L22 15.6863V6C22 5.44772 21.5523 5 21 5ZM21 19H10.1422L15.6066 13.5356C15.9971 13.145 16.6303 13.145 17.0208 13.5356L21.907 18.4217C21.7479 18.7633 21.4016 19 21 19Z"
fill="currentColor" />
</svg>
</label>
</div>
<button onclick="create()" name="account">Сохранить</button>
</div>
</div>
{%endif%}
{%endif%}
<div class="content-news" id="publications">
{% for publication in publications %}
{% include '/themes/test/publication.html' %}
{% endfor %}
</div>
{% if errors is not empty %}
{% if errors.account is not empty %}
<ul class="errors">
{% for error in errors.account %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{% endif %}
{% if account.permissions.publications is defined and account.permissions.publications == 1 %}
<script type="text/javascript" src="/js/publication/trash.js"></script>
<script type="text/javascript" src="/js/publication/update.js"></script>
<script type="text/javascript" src="/js/publication/create.js"></script>
{%endif%}
</div>

View File

@@ -0,0 +1,5 @@
<link href="/css/banners.css" rel="stylesheet">
{% include '/themes/default/authentication.html' %}
{% include '/themes/default/vk.html' %}

View File

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