languages + close button + import fix + total rebuild

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2024-10-12 19:19:55 +03:00
parent 8de9a0b158
commit 5fa4abba5f
25 changed files with 581 additions and 396 deletions

View File

@ -35,11 +35,15 @@ final class catalog extends core
*/ */
public function index(array $parameters = []): ?string public function index(array $parameters = []): ?string
{ {
if (!empty($parameters['category'])) { // Initializing identifier of a category
preg_match('/[\d]+/', $parameters['identifier'] ?? '', $matches);
$identifier = $matches[0] ?? null;
if (!empty($parameters['identifier'])) {
// Передана категория (идентификатор) // Передана категория (идентификатор)
// Инициализация актуальной категории // Инициализация актуальной категории
$category = new category(document: category::_read('d.identifier == @identifier', parameters: ['identifier' => $parameters['category']])); $category = category::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $identifier], errors: $this->errors['catalog']);
if ($category instanceof category) { if ($category instanceof category) {
// Found the category // Found the category
@ -84,7 +88,10 @@ final class catalog extends core
// Generating filters // Generating filters
$this->view->filters = [ $this->view->filters = [
'brands' => product::collect('d.brand.ru', $this->errors['catalog']) 'brands' => product::collect(
'd.brand.' . $this->language->name,
errors: $this->errors['catalog']
)
]; ];
if ($_SERVER['REQUEST_METHOD'] === 'GET') { if ($_SERVER['REQUEST_METHOD'] === 'GET') {
@ -110,7 +117,7 @@ final class catalog extends core
'html' => [ 'html' => [
'categories' => $this->view->render('catalog/elements/categories.html'), 'categories' => $this->view->render('catalog/elements/categories.html'),
'products' => $this->view->render('catalog/elements/products/2columns.html'), 'products' => $this->view->render('catalog/elements/products/2columns.html'),
'filters' => $this->view->render('catalog/elemments/filters.html') 'filters' => $this->view->render('catalog/elements/filters.html')
], ],
'errors' => $this->errors 'errors' => $this->errors
] ]
@ -151,6 +158,7 @@ final class catalog extends core
filter: 'd.deleted != true && d.hidden != true', filter: 'd.deleted != true && d.hidden != true',
sort: 'd.position ASC, d.name ASC, d.created DESC', sort: 'd.position ASC, d.name ASC, d.created DESC',
amount: 30, amount: 30,
language: $this->language,
errors: $this->errors['catalog'] errors: $this->errors['catalog']
) : []; ) : [];
@ -167,7 +175,7 @@ final class catalog extends core
[ [
'title' => $title ?? '', 'title' => $title ?? '',
'html' => [ 'html' => [
'products' => $this->view->render('catalog/elements/products.html') 'products' => $this->view->render('catalog/elements/products/2columns.html')
], ],
'errors' => $this->errors 'errors' => $this->errors
] ]
@ -195,53 +203,27 @@ final class catalog extends core
*/ */
public function product(array $parameters = []): ?string public function product(array $parameters = []): ?string
{ {
// Initializing of text fore search // Initializing identifier of a product
preg_match('/[\d]+/', $parameters['id'] ?? '', $matches); preg_match('/[\d]+/', $parameters['identifier'] ?? '', $matches);
$_key = $matches[0] ?? null; $identifier = $matches[0] ?? null;
if (!empty($_key)) { if (!empty($identifier)) {
// Received id of prouct (_key) // Received identifier of the product
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// POST request // POST request
// Search for products // Search for products
$product = product::read( $product = product::read(
filter: "d._key == \"$_key\" && d.deleted != true && d.hidden != true", filter: "d.identifier == @identifier && d.deleted != true && d.hidden != true",
sort: 'd.created DESC', sort: 'd.created DESC',
amount: 1, amount: 1,
return: '{id: d._key, title: d.title.ru, description: d.description.ru, cost: d.cost, weight: d.weight, dimensions: d.dimensions, brand: d.brand.ru, compatibility: d.compatibility.ru}', return: '{identifier: d.identifier, name: d.name.@language, description: d.description.@language, cost: d.cost, weight: d.weight, dimensions: d.dimensions, brand: d.brand.@language, compatibility: d.compatibility.@language, images: d.images[*].storage}',
parameters: ['identifier' => (int) $identifier],
language: $this->language,
errors: $this->errors['catalog'] errors: $this->errors['catalog']
)[0]?->getAll(); )[0]?->getAll();
if (!empty($product)) {
// Found the product
// Initializing buffer of images
$images = [];
foreach (
glob(INDEX .
DIRECTORY_SEPARATOR .
'themes' .
DIRECTORY_SEPARATOR .
(THEME ?? 'default') .
DIRECTORY_SEPARATOR .
'images' .
DIRECTORY_SEPARATOR .
$_key .
DIRECTORY_SEPARATOR .
"*.{jpg,png,gif}", GLOB_BRACE) as $file
) {
// Iterate over images of the product
// Write to buffer of images
$images[] = "/images/$_key/" . basename($file);
}
$product = $product + ['images' => $images];
}
// Initializing a response headers // Initializing a response headers
header('Content-Type: application/json'); header('Content-Type: application/json');
header('Content-Encoding: none'); header('Content-Encoding: none');

View File

@ -10,7 +10,8 @@ use mirzaev\arming_bot\views\templater,
mirzaev\arming_bot\models\account, mirzaev\arming_bot\models\account,
mirzaev\arming_bot\models\session, mirzaev\arming_bot\models\session,
mirzaev\arming_bot\models\settings, mirzaev\arming_bot\models\settings,
mirzaev\arming_bot\models\suspension; mirzaev\arming_bot\models\suspension,
mirzaev\arming_bot\models\enumerations\language;
// Framework for PHP // Framework for PHP
use mirzaev\minimal\controller; use mirzaev\minimal\controller;
@ -31,7 +32,7 @@ class core extends controller
/** /**
* Instance of the settings * Instance of the settings
*/ */
public static settings $settings; protected readonly settings $settings;
/** /**
* Instance of a session * Instance of a session
@ -43,6 +44,11 @@ class core extends controller
*/ */
protected readonly ?account $account; protected readonly ?account $account;
/**
* Language
*/
protected language $language = language::en;
/** /**
* Registry of errors * Registry of errors
*/ */
@ -57,6 +63,8 @@ class core extends controller
* @param bool $initialize Initialize a controller? * @param bool $initialize Initialize a controller?
* *
* @return void * @return void
*
* @todo settings account и session не имеют проверок на возврат null
*/ */
public function __construct(bool $initialize = true) public function __construct(bool $initialize = true)
{ {
@ -70,7 +78,7 @@ class core extends controller
// Initializing is requested // Initializing is requested
// Initializing of models core (connect to ArangoDB...) // Initializing of models core (connect to ArangoDB...)
new models(); new models(true);
// Initializing of the date until which the session will be active // Initializing of the date until which the session will be active
$expires = strtotime('+1 week'); $expires = strtotime('+1 week');
@ -106,10 +114,18 @@ class core extends controller
$this->account = $this->session->account($this->errors['account']); $this->account = $this->session->account($this->errors['account']);
// Initializing of the settings // Initializing of the settings
self::$settings = settings::active(); $this->settings = settings::active();
// Initializing of language
if ($this->account?->language) $this->language = $this->account->language ?? language::en;
else if ($this->settings?->language) $this->language = $this->settings->language ?? language::en;
// Initializing of preprocessor of views // Initializing of preprocessor of views
$this->view = new templater($this->session, $this->account); $this->view = new templater(
session: $this->session,
account: $this->account,
settings: $this->settings
);
// @todo перенести в middleware // @todo перенести в middleware
@ -142,20 +158,20 @@ class core extends controller
suspension: suspension:
// Write title of the page to templater global variables // Write title of the page to templater global variables
$this->view->title = match ($account?->language ?? self::$settings?->language) { $this->view->title = match ($this->language) {
'ru' => 'Приостановлено', language::en => 'Suspended',
'en' => 'Suspended', language::ru => 'Приостановлено',
default => 'Suspended' default => 'Suspended'
}; };
// Write description of the suspension to templater global variables // Write description of the suspension to templater global variables
$this->view->description = $suspension->description[$account?->language ?? self::$settings?->language] ?? array_values($suspension->description)[0]; $this->view->description = $suspension->description[$this->language] ?? array_values($suspension->description)[0];
// Write message of remaining time of the suspension to templater global variables // Write message of remaining time of the suspension to templater global variables
$this->view->remain = [ $this->view->remain = [
'title' => match ($account?->language ?? self::$settings?->language) { 'title' => match ($this->language) {
'ru' => 'Осталось времени: ', language::en => 'Time remaining: ',
'en' => 'Time remaining: ', language::ru => 'Осталось времени: ',
default => 'Time remaining: ' default => 'Time remaining: '
}, },
'value' => $suspension?->message() 'value' => $suspension?->message()

View File

@ -8,7 +8,8 @@ namespace mirzaev\arming_bot\models;
use mirzaev\arming_bot\models\core, use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\traits\status, mirzaev\arming_bot\models\traits\status,
mirzaev\arming_bot\models\traits\document as arangodb_document_trait, mirzaev\arming_bot\models\traits\document as arangodb_document_trait,
mirzaev\arming_bot\models\interfaces\document as arangodb_document_interface; mirzaev\arming_bot\models\interfaces\document as arangodb_document_interface,
mirzaev\arming_bot\models\enumerations\language;
// Framework for ArangoDB // Framework for ArangoDB
use mirzaev\arangodb\collection, use mirzaev\arangodb\collection,
@ -17,6 +18,9 @@ use mirzaev\arangodb\collection,
// Framework for Telegram // Framework for Telegram
use Zanzara\Telegram\Type\User as telegram; use Zanzara\Telegram\Type\User as telegram;
// Library for ArangoDB
use ArangoDBClient\Document as _document;
// Built-in libraries // Built-in libraries
use exception; use exception;
@ -52,10 +56,8 @@ final class account extends core implements arangodb_document_interface
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
// Initialized the collection // Initialized the collection
try { // Initializing the account
// Initializing the account and exit (success) $result = collection::execute(
return new static(
document: collection::execute(
<<<'AQL' <<<'AQL'
FOR d IN @@collection FOR d IN @@collection
FILTER d.identifier == @identifier FILTER d.identifier == @identifier
@ -66,14 +68,31 @@ final class account extends core implements arangodb_document_interface
'identifier' => $identifier 'identifier' => $identifier
], ],
errors: $errors errors: $errors
)
); );
} catch (exception $e) {
if ($registration) { if ($result instanceof _document) {
// Initialized the account
// Initializing the object
$account = new account;
if (method_exists($account, '__document')) {
// Object can implement a document from ArangoDB
// Abstractioning of parameters
$result->language = language::{$result->language} ?? 'en';
// Writing the instance of account document from ArangoDB to the implement object
$account->__document($result);
// Exit (success)
return $account;
}
} else if ($registration) {
// Not found the account and registration is requested // Not found the account and registration is requested
// Creating account // Creating account
$account = document::write( $_id = document::write(
static::COLLECTION, static::COLLECTION,
(is_array($registration) (is_array($registration)
? $registration : ? $registration :
@ -100,7 +119,7 @@ final class account extends core implements arangodb_document_interface
'messages' => $registration->getCanReadAllGroupMessages() 'messages' => $registration->getCanReadAllGroupMessages()
], ],
'premium' => $registration->isPremium(), 'premium' => $registration->isPremium(),
'language' => $registration->getLanguageCode(), 'language' => language::{$registration->getLanguageCode()}->name ?? 'en',
'queries' => [ 'queries' => [
'inline' => $registration->getSupportsInlineQueries() 'inline' => $registration->getSupportsInlineQueries()
] ]
@ -111,14 +130,13 @@ final class account extends core implements arangodb_document_interface
errors: $errors errors: $errors
); );
if ($account) { if ($_id) {
// Created account // Created account
// Initializing of the account (without registration request) // Initializing of the account (without registration request)
return static::initialize($identifier, errors: $errors); return static::initialize($identifier, errors: $errors);
} else throw new exception('Failed to register account'); } else throw new exception('Failed to register account');
} else throw new exception('Failed to find account'); } else throw new exception('Failed to find account');
}
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} catch (exception $e) { } catch (exception $e) {
// Writing to the registry of errors // Writing to the registry of errors

View File

@ -10,6 +10,7 @@ use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\category, mirzaev\arming_bot\models\category,
mirzaev\arming_bot\models\entry, mirzaev\arming_bot\models\entry,
mirzaev\arming_bot\models\traits\files, mirzaev\arming_bot\models\traits\files,
mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\traits\yandex\disk as yandex; mirzaev\arming_bot\models\traits\yandex\disk as yandex;
// Framework for ArangoDB // Framework for ArangoDB
@ -52,7 +53,7 @@ final class catalog extends core
* @param int &$products_deleted Counter of deleted products * @param int &$products_deleted Counter of deleted products
* @param int &$products_new Counter of new products * @param int &$products_new Counter of new products
* @param int &$products_old Counter of old products * @param int &$products_old Counter of old products
* @param string &$language Language (en, ru...) * @param language $language Language
* @param array &$errors Registry of errors * @param array &$errors Registry of errors
* *
* @return void * @return void
@ -75,7 +76,7 @@ final class catalog extends core
int &$products_deleted = 0, int &$products_deleted = 0,
int &$products_old = 0, int &$products_old = 0,
int &$products_new = 0, int &$products_new = 0,
string $language = 'en', language $language = language::en,
array &$errors = [] array &$errors = []
): void { ): void {
try { try {
@ -112,7 +113,7 @@ final class catalog extends core
try { try {
if (!empty($row['identifier']) && !empty($row['name'])) { if (!empty($row['identifier']) && !empty($row['name'])) {
// All required cells are filled in // Required cells are filled in
// Incrementing the counter of loaded categories // Incrementing the counter of loaded categories
++$categories_loaded; ++$categories_loaded;
@ -123,36 +124,40 @@ final class catalog extends core
// Declaring the variable with the category // Declaring the variable with the category
$category = null; $category = null;
try {
// Initializing the category // Initializing the category
$category = new category(document: category::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $row['identifier']], errors: $errors)[0] ?? null); $category = category::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $row['identifier']], errors: $errors);
if ($category instanceof category) {
// Initialized the category
// Initializing name of the category // Initializing name of the category
$category->name[$language] === $row['name'] || $category->name = [[$language => $row['name']]] + $category->name ?? []; if (empty($category->name) || empty($category->name[$language->name]) || $category->name[$language->name] !== $row['name'])
$category->name = [$language->name => $row['name']] + ($category->name ?? []);
// Initializing position of the category // Initializing position of the category
$category->position === $row['position'] || $category->position = $row['position']; if (empty($category->position) || $category->position === $row['position'])
} catch (exception $e) { $category->position = $row['position'];
// Not found the category } else {
// Not initialized the category
// Creating the category // Creating the category
$_id = category::write((int) $row['identifier'], [$language => $row['name']], $row['position'] ?? null, $errors); $_id = $created = category::write((int) $row['identifier'], [$language->name => $row['name']], $row['position'] ?? null, $errors);
// Initializing the category // Initializing the category
$category = new category(document: $created = category::_read('d._id == @_id', parameters: ['_id' => $_id], errors: $errors)[0] ?? null); $category = category::_read('d._id == @_id', parameters: ['_id' => $_id], errors: $errors);
// Incrementing the counter of created categories // Incrementing the counter of created categories
if ($created) ++$categories_created; if ($created) ++$categories_created;
}; };
if ($category instanceof category) { if ($category instanceof category) {
// Found the category // Initialized the category
if (!empty($row['category'])) { if (!empty($row['category'])) {
// Received the ascendant category // Received the ascendant category
// Initializing the ascendant category // Initializing the ascendant category
$ascendant = new category(document: category::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $row['category']], errors: $errors)[0] ?? null); $ascendant = category::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $row['category']], errors: $errors);
if ($ascendant instanceof category) { if ($ascendant instanceof category) {
// Found the ascendant category // Found the ascendant category
@ -265,7 +270,7 @@ final class catalog extends core
try { try {
if (!empty($row['identifier']) && !empty($row['name'])) { if (!empty($row['identifier']) && !empty($row['name'])) {
// All required cells are filled in // Required cells are filled in
// Incrementing the counter of loaded products // Incrementing the counter of loaded products
++$products_loaded; ++$products_loaded;
@ -276,45 +281,63 @@ final class catalog extends core
// Declaring the variable with the product // Declaring the variable with the product
$product = null; $product = null;
try {
// Initializing the product // Initializing the product
$product = new product(document: product::_read('d.identifier == %u', parameters: ['identifier' => (int) $row['identifier']], errors: $errors)[0] ?? null); $product = product::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $row['identifier']], errors: $errors);
// Initializing name of the category if ($product instanceof product) {
$product->name[$language] === $row['name'] || $product->name = [[$language => $row['name']]] + $product->name ?? []; // Initialized the product
// Initializing name of the product
if (empty($product->name) || empty($product->name[$language->name]) || $product->name[$language->name] !== $row['name'])
$product->name = [$language->name => $row['name']] + ($product->name ?? []);
// Initializing description of the product
if (empty($product->description) || empty($product->description[$language->name]) || $product->description[$language->name] !== $row['description'])
$product->description = [$language->name => $row['description']] + ($product->description ?? []);
// Initializing brand of the product
if (empty($product->brand) || empty($product->brand[$language->name]) || $product->brand[$language->name] !== $row['brand'])
$product->brand = [$language->name => $row['brand']] + ($product->brand ?? []);
// Initializing compatibility of the product
if (empty($product->compatibility) || empty($product->brand[$language->name]) || $product->compatibility[$language->name] !== $row['compatibility'])
$product->compatibility = [$language->name => $row['compatibility']] + ($product->compatibility ?? []);
// Initializing position of the product // Initializing position of the product
$product->position === $row['position'] || $product->position = $row['position']; if (empty($product->position) || $product->position !== $row['position'])
} catch (exception $e) { $product->position = $row['position'];
// Not found the product } else {
// Not initialized the product
// Creating the product // Creating the product
$_id = product::write( $_id = product::write(
(int) $row['identifier'], (int) $row['identifier'],
[$language => $row['name']], [$language->name => $row['name']],
[$language => $row['description']], [$language->name => $row['description']],
(float) $row['cost'], (float) $row['cost'],
(float) $row['weight'], (float) $row['weight'],
['x' => $row['x'], 'y' => $row['y'], 'z' => $row['z']], ['x' => $row['x'], 'y' => $row['y'], 'z' => $row['z']],
[$language->name => $row['brand']],
[$language->name => $row['compatibility']],
$row['position'] ?? null, $row['position'] ?? null,
errors: $errors errors: $errors
); );
// Initializing the product // Initializing the product
$product = new product(document: $created = product::_read(sprintf('d._id == "%s"', $_id), errors: $errors)[0] ?? null); $product = $created = product::_read('d._id == @_id', parameters: ['_id' => $_id], errors: $errors);
// Incrementing the counter of created products // Incrementing the counter of created products
if ($created) ++$products_created; if ($created) ++$products_created;
} }
if ($product instanceof product) { if ($product instanceof product) {
// Found the product // Initialized the product
if (!empty($row['category'])) { if (!empty($row['category'])) {
// Received the category // Received the category
// Initializing the category // Initializing the category
$category = new category(document: category::_read(sprintf('d.identifier == %u', (int) $row['category']), errors: $errors)[0] ?? null); $category = category::_read(sprintf('d.identifier == %u', (int) $row['category']), errors: $errors);
if ($category instanceof category) { if ($category instanceof category) {
// Found the ascendant category // Found the ascendant category
@ -410,7 +433,7 @@ final class catalog extends core
sort: 'd.updated DESC', sort: 'd.updated DESC',
amount: 100000, amount: 100000,
errors: $errors errors: $errors
) as $document ) ?? [] as $document
) { ) {
// Iterating over categories // Iterating over categories
@ -444,7 +467,7 @@ final class catalog extends core
sort: 'd.updated DESC', sort: 'd.updated DESC',
amount: 100000, amount: 100000,
errors: $errors errors: $errors
) as $document ) ?? [] as $document
) { ) {
// Iterating over products // Iterating over products

View File

@ -12,7 +12,7 @@ use mirzaev\arangodb\connection as arangodb,
mirzaev\arangodb\collection, mirzaev\arangodb\collection,
mirzaev\arangodb\enumerations\collection\type; mirzaev\arangodb\enumerations\collection\type;
// Libraries for ArangoDB // Library for ArangoDB
use ArangoDBClient\Document as _document; use ArangoDBClient\Document as _document;
// Built-in libraries // Built-in libraries
@ -40,6 +40,8 @@ class core extends model
/** /**
* Instance of the session of ArangoDB * Instance of the session of ArangoDB
*
* @todo ПЕРЕДЕЛАТЬ В php 8.4
*/ */
protected static arangodb $arangodb; protected static arangodb $arangodb;
@ -61,7 +63,7 @@ class core extends model
* *
* @return void * @return void
*/ */
public function __construct(bool $initialize = true, ?arangodb $arangodb = null) public function __construct(bool $initialize = false, ?arangodb $arangodb = null)
{ {
// For the extends system // For the extends system
parent::__construct($initialize); parent::__construct($initialize);
@ -69,17 +71,8 @@ class core extends model
if ($initialize) { if ($initialize) {
// Initializing is requested // Initializing is requested
if (isset($arangodb)) {
// Recieved an instance of a session of ArangoDB
// Writing an instance of a session of ArangoDB to the property // Writing an instance of a session of ArangoDB to the property
$this->__set('arangodb', $arangodb); self::$arangodb = $arangodb ?? new arangodb(require static::ARANGODB);
} else {
// Not recieved an instance of a session of ArangoDB
// Initializing of an instance of a session of ArangoDB
$this->__get('arangodb');
}
} }
} }
@ -104,7 +97,7 @@ class core extends model
string $return = 'd', string $return = 'd',
array $parameters = [], array $parameters = [],
array &$errors = [] array &$errors = []
): _document|array|null { ): _document|static|array|null {
try { try {
if (collection::initialize(static::COLLECTION, static::TYPE)) { if (collection::initialize(static::COLLECTION, static::TYPE)) {
// Initialized the collection // Initialized the collection
@ -117,22 +110,39 @@ class core extends model
%s %s
%s %s
LIMIT @offset, @amount LIMIT @offset, @amount
RETURN @return RETURN %s
AQL, AQL,
empty($filter) ? '' : "FILTER $filter", empty($filter) ? '' : "FILTER $filter",
empty($sort) ? '' : "SORT $sort", empty($sort) ? '' : "SORT $sort",
empty($return) ? 'd' : $return
), ),
[ [
'@collection' => static::COLLECTION, '@collection' => static::COLLECTION,
'offset' => --$page <= 0 ? 0 : $page * $amount, 'offset' => --$page <= 0 ? 0 : $page * $amount,
'amount' => $amount, 'amount' => $amount
'return' => $return
] + $parameters, ] + $parameters,
errors: $errors errors: $errors
); );
if ($result instanceof _document) {
// Received only 1 document and
// Initializing the object
$object = new static;
if (method_exists($object, '__document')) {
// Object can implement a document from ArangoDB
// Writing the instance of document from ArangoDB to the implement object
$object->__document($result);
// Exit (success) // Exit (success)
return is_array($result) ? $result : [$result]; return $object;
}
}
// Exit (success)
return $result;
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} catch (exception $e) { } catch (exception $e) {
// Writing to registry of errors // Writing to registry of errors
@ -160,26 +170,9 @@ class core extends model
{ {
match ($name) { match ($name) {
'arangodb' => (function () use ($value) { 'arangodb' => (function () use ($value) {
if ($this->__isset('arangodb')) { if (isset(static::$arangodb)) throw new exception('Forbidden to reinitialize the session of ArangoDB ($this::$arangodb)', 500);
// Is alredy initialized else if ($value instanceof arangodb) self::$arangodb = $value;
else throw new exception('Session of connection to ArangoDB ($this::$arangodb) is need to be mirzaev\arangodb\connection', 500);
// Exit (fail)
throw new exception('Forbidden to reinitialize the session of ArangoDB ($this::$arangodb)', 500);
} else {
// Is not already initialized
if ($value instanceof arangodb) {
// Recieved an appropriate value
// Writing the property and exit (success)
self::$arangodb = $value;
} else {
// Recieved an inappropriate value
// Exit (fail)
throw new exception('Session of ArangoDB ($this::$arangodb) is need to be mirzaev\arangodb\connection', 500);
}
}
})(), })(),
default => parent::__set($name, $value) default => parent::__set($name, $value)
}; };
@ -195,22 +188,6 @@ class core extends model
public function __get(string $name): mixed public function __get(string $name): mixed
{ {
return match ($name) { return match ($name) {
'arangodb' => (function () {
try {
if (!$this->__isset('arangodb')) {
// Is not initialized
// Initializing of a default value from settings
$this->__set('arangodb', new arangodb(require static::ARANGODB));
}
// Exit (success)
return self::$arangodb;
} catch (exception) {
// Exit (fail)
return null;
}
})(),
default => parent::__get($name) default => parent::__get($name)
}; };
} }

View File

@ -227,7 +227,8 @@ final class entry extends core implements arangodb_document_interface
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
// Initialized collections // Initialized collections
if ($documents = collection::execute( // Execute and exit (success)
return is_array($result = collection::execute(
sprintf( sprintf(
<<<'AQL' <<<'AQL'
FOR v IN 1..1 INBOUND @document GRAPH @graph FOR v IN 1..1 INBOUND @document GRAPH @graph
@ -241,18 +242,13 @@ final class entry extends core implements arangodb_document_interface
empty($sort) ? '' : "SORT $sort", empty($sort) ? '' : "SORT $sort",
), ),
[ [
'grapth' => 'catalog', 'graph' => 'catalog',
'document' => $document->getId(), 'document' => $document->getId(),
'offset' => --$page <= 0 ? $page = 0 : $page * $amount, 'offset' => --$page <= 0 ? $page = 0 : $page * $amount,
'amount' => $amount 'amount' => $amount
], ],
errors: $errors errors: $errors
)) { )) ? $result : [$result];
// Fount entries
// Возврат (успех)
return is_array($documents) ? $documents : [$documents];
} else return [];
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . $document::TYPE . ' collection: ' . $document::COLLECTION); } else throw new exception('Failed to initialize ' . $document::TYPE . ' collection: ' . $document::COLLECTION);
} catch (exception $e) { } catch (exception $e) {

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace mirzaev\arming_bot\models\enumerations;
/**
* Types of human languages
*
* @package mirzaev\arming_bot\models\enumerations
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
enum language
{
case en;
case ru;
/**
* Translate label of language
*
* @param language|null $language Language into which to translate
*
* @return string Translated label of language
*
* @todo
* 1. More languages
* 2. Cases???
*/
public function translate(?language $language = language::en): string
{
// Exit (success)
return match ($this) {
language::en => match ($language) {
language::en => 'English',
language::ru => 'Английский'
},
language::ru => match ($language) {
language::en => 'Russian',
language::ru => 'Русский'
}
};
}
}

View File

@ -4,6 +4,14 @@ declare(strict_types=1);
namespace mirzaev\arming_bot\models\enumerations; namespace mirzaev\arming_bot\models\enumerations;
/**
* Types of session verification
*
* @package mirzaev\arming_bot\models\enumerations
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
enum session enum session
{ {
case hash_only; case hash_only;

View File

@ -7,9 +7,6 @@ namespace mirzaev\arming_bot\models\interfaces;
// Library для ArangoDB // Library для ArangoDB
use ArangoDBClient\Document as _document; use ArangoDBClient\Document as _document;
// Framework for ArangoDB
use mirzaev\arangodb\connection as arangodb;
/** /**
* Interface for implementing a document instance from ArangoDB * Interface for implementing a document instance from ArangoDB
* *

View File

@ -6,6 +6,7 @@ namespace mirzaev\arming_bot\models;
// Files of the project // Files of the project
use mirzaev\arming_bot\models\core, use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\traits\document as arangodb_document_trait; mirzaev\arming_bot\models\traits\document as arangodb_document_trait;
// Framework for ArangoDB // Framework for ArangoDB
@ -41,6 +42,8 @@ final class product extends core
* @param float $cost Cost * @param float $cost Cost
* @param float $weight Weight * @param float $weight Weight
* @param array $dimensions Dimensions ['x' => 0.0, 'y' => 0.0, 'z' => 0.0] * @param array $dimensions Dimensions ['x' => 0.0, 'y' => 0.0, 'z' => 0.0]
* @param array|null $brand Brand [['en' => value], ['ru' => значение]]
* @param array|null $compatibility Compatibility [['en' => value], ['ru' => значение]]
* @param array $images Images (first will be thumbnail) * @param array $images Images (first will be thumbnail)
* @param int|null $position Position for sorting in the catalog (ASC) * @param int|null $position Position for sorting in the catalog (ASC)
* @param array $data Data * @param array $data Data
@ -58,6 +61,8 @@ final class product extends core
float $cost = 0, float $cost = 0,
float $weight = 0, float $weight = 0,
array $dimensions = ['x' => 0, 'y' => 0, 'z' => 0], array $dimensions = ['x' => 0, 'y' => 0, 'z' => 0],
?array $brand = [['en' => 'ERROR']],
?array $compatibility = [['en' => 'ERROR']],
array $images = [], array $images = [],
?int $position = null, ?int $position = null,
array $data = [], array $data = [],
@ -81,6 +86,8 @@ final class product extends core
'y' => $dimensions['y'] ?? 0, 'y' => $dimensions['y'] ?? 0,
'z' => $dimensions['z'] ?? 0, 'z' => $dimensions['z'] ?? 0,
], ],
'brand' => $brand,
'compatibility' => $compatibility,
'images' => $images, 'images' => $images,
'position' => $position, 'position' => $position,
'version' => ROBOT_VERSION 'version' => ROBOT_VERSION
@ -111,7 +118,8 @@ final class product extends core
* @param int $page Page * @param int $page Page
* @param int $amount Amount per page * @param int $amount Amount per page
* @param string|null $return Return (AQL) * @param string|null $return Return (AQL)
* @param string $language Language code (en, ru...) * @param language $language Language code (en, ru...)
* @param array $parameters Binded parameters for placeholders ['placeholder' => parameter]
* @param array &$errors Registry of errors * @param array &$errors Registry of errors
* *
* @return array Массив с найденными товарами (может быть пустым) * @return array Массив с найденными товарами (может быть пустым)
@ -123,23 +131,31 @@ final class product extends core
int $page = 1, int $page = 1,
int $amount = 100, int $amount = 100,
?string $return = 'd', ?string $return = 'd',
string $language = 'en', language $language = language::en,
array $parameters = [],
array &$errors = [] array &$errors = []
): array { ): array {
try { try {
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
// Initialized the collection // Initialized the collection
// Initializing the query // Initializing parameters for search
$aql = <<<'AQL' if ($search) $parameters += [
FOR d IN @@collection 'search' => $search,
AQL; 'analyzer' => 'text_' . $language->name
];
if ($search) { // Reading products
// Requested search $documents = collection::execute(
sprintf(
// Writing to the query <<<'AQL'
$aql .= <<<'AQL' FOR d IN @@collection %s
%s
%s
LIMIT @offset, @amount
RETURN %s
AQL,
empty($search) ? '' : <<<'AQL'
SEARCH SEARCH
LEVENSHTEIN_MATCH( LEVENSHTEIN_MATCH(
d.name.@language, d.name.@language,
@ -159,34 +175,17 @@ final class product extends core
1, 1,
false false
) )
AQL;
// Adding sorting
if ($sort) $sort = "BM25(d) DESC, $sort";
else $sort = "BM25(d) DESC";
}
// Reading products
$documents = collection::execute(
sprintf(
$aql . <<<'AQL'
%s
%s
LIMIT @offset, @amount
RETURN $s
AQL, AQL,
empty($filter) ? '' : "FILTER $filter", empty($filter) ? '' : "FILTER $filter",
empty($sort) ? '' : "SORT $sort", empty($search) ? (empty($sort) ? '' : "SORT $sort") : (empty($sort) ? "SORT BM25(d) DESC" : "SORT BM25(d) DESC, $sort"),
empty($return) ? 'd' : $return empty($return) ? 'd' : $return
), ),
[ [
'@collection' => $search ? static::COLLECTION . 's_search' : static::COLLECTION, '@collection' => empty($search) ? static::COLLECTION : static::COLLECTION . 's_search',
'search' => $search, 'language' => $language->name,
'language' => $language,
'analyzer' => "text_$language",
'offset' => --$page <= 0 ? $page = 0 : $page * $amount, 'offset' => --$page <= 0 ? $page = 0 : $page * $amount,
'amount' => $amount, 'amount' => $amount,
], ] + $parameters,
errors: $errors errors: $errors
); );
@ -196,7 +195,7 @@ final class product extends core
// Exit (success) // Exit (success)
return is_array($documents) ? $documents : [$documents]; return is_array($documents) ? $documents : [$documents];
} else return []; } else return [];
} else throw new exception('Failed to initialize ' . static::COLLECTION . ' collection: ' . static::COLLECTION); } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} catch (exception $e) { } catch (exception $e) {
// Writing to the registry of errors // Writing to the registry of errors
$errors[] = [ $errors[] = [
@ -227,9 +226,9 @@ final class product extends core
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
// Initialized the collection // Initialized the collection
if ($values = collection::execute( if ($result = collection::execute(
<<<'AQL' <<<'AQL'
FOR d IN @@collecton FOR d IN @@collection
RETURN DISTINCT @parameter RETURN DISTINCT @parameter
AQL, AQL,
[ [
@ -241,7 +240,7 @@ final class product extends core
// Found parameters // Found parameters
// Exit (success) // Exit (success)
return $values; return is_array($result) ? $result : [$result];
} else return []; } else return [];
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} catch (exception $e) { } catch (exception $e) {

View File

@ -141,7 +141,7 @@ final class session extends core implements arangodb_document_interface
// Initialized collections // Initialized collections
// Search for connected account // Search for connected account
$document = collection::execute( $result = collection::execute(
<<<AQL <<<AQL
FOR v IN INBOUND @session GRAPH sessions FOR v IN INBOUND @session GRAPH sessions
SORT v.created DESC SORT v.created DESC
@ -154,17 +154,21 @@ final class session extends core implements arangodb_document_interface
errors: $errors errors: $errors
); );
if ($document instanceof _document) { if ($result instanceof _document) {
// Found connected account // Found active settings
// Initializing the implement object of the instance of sesson document from ArangoDB // Initializing the object
$account = new account; $account = new static;
// Writing the instance of session document from ArangoDB to the implement object if (method_exists($account, '__document')) {
$account->__document($document); // Object can implement a document from ArangoDB
// Writing the instance of account document from ArangoDB to the implement object
$account->__document($result);
// Exit (success) // Exit (success)
return $account; return $account;
}
} else return null; } else return null;
} else throw new exception('Failed to initialize ' . account::TYPE . ' collection: ' . account::COLLECTION); } else throw new exception('Failed to initialize ' . account::TYPE . ' collection: ' . account::COLLECTION);
} else throw new exception('Failed to initialize ' . connect::TYPE . ' collection: ' . connect::COLLECTION); } else throw new exception('Failed to initialize ' . connect::TYPE . ' collection: ' . connect::COLLECTION);

View File

@ -7,7 +7,8 @@ namespace mirzaev\arming_bot\models;
// Files of the project // Files of the project
use mirzaev\arming_bot\models\core, use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\traits\document as arangodb_document_trait, mirzaev\arming_bot\models\traits\document as arangodb_document_trait,
mirzaev\arming_bot\models\interfaces\document as arangodb_document_interface; mirzaev\arming_bot\models\interfaces\document as arangodb_document_interface,
mirzaev\arming_bot\models\enumerations\language;
// Framework for ArangoDB // Framework for ArangoDB
use mirzaev\arangodb\collection, use mirzaev\arangodb\collection,
@ -51,7 +52,7 @@ final class settings extends core implements arangodb_document_interface
// Initialized the collection // Initialized the collection
// Search for active settings // Search for active settings
$document = collection::execute( $result = collection::execute(
<<<'AQL' <<<'AQL'
FOR d IN @@collection FOR d IN @@collection
FILTER d.status == 'active' FILTER d.status == 'active'
@ -65,17 +66,24 @@ final class settings extends core implements arangodb_document_interface
errors: $errors errors: $errors
); );
if ($document instanceof _document) { if ($result instanceof _document) {
// Found active settings // Found active settings
// Initializing the implement object of the instance of settings document from ArangoDB // Initializing the object
$settings = new static; $settings = new static;
if (method_exists($settings, '__document')) {
// Object can implement a document from ArangoDB
// Abstractioning of parameters
$result->language = language::{$result->language} ?? 'en';
// Writing the instance of settings document from ArangoDB to the implement object // Writing the instance of settings document from ArangoDB to the implement object
$settings->__document($document); $settings->__document($result);
// Exit (success) // Exit (success)
return $settings; return $settings;
}
} else if ($create) { } else if ($create) {
// Not found active settings and requested their creating // Not found active settings and requested their creating

View File

@ -6,8 +6,7 @@ namespace mirzaev\arming_bot\models;
// Files of the project // Files of the project
use mirzaev\arming_bot\models\core, use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\controllers\core as controller, mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\settings,
mirzaev\arming_bot\models\traits\document as arangodb_document_trait, mirzaev\arming_bot\models\traits\document as arangodb_document_trait,
mirzaev\arming_bot\models\interfaces\document as arangodb_document_interface; mirzaev\arming_bot\models\interfaces\document as arangodb_document_interface;
@ -53,7 +52,7 @@ final class suspension extends core implements arangodb_document_interface
// Initialized the collection // Initialized the collection
// Search for active suspension // Search for active suspension
$document = collection::execute( $result = collection::execute(
<<<'AQL' <<<'AQL'
FOR d IN @@collection FOR d IN @@collection
FILTER d.end > @time FILTER d.end > @time
@ -68,17 +67,21 @@ final class suspension extends core implements arangodb_document_interface
errors: $errors errors: $errors
); );
if ($document instanceof _document) { if ($result instanceof _document) {
// Found active suspension // Found active settings
// Initializing the implement object of the instance of suspension document from ArangoDB // Initializing the object
$suspension = new static; $suspension = new static;
if (method_exists($suspension, '__document')) {
// Object can implement a document from ArangoDB
// Writing the instance of suspension document from ArangoDB to the implement object // Writing the instance of suspension document from ArangoDB to the implement object
$suspension->__document($document); $suspension->__document($result);
// Exit (success) // Exit (success)
return $suspension; return $suspension;
}
} else return null; } else return null;
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} catch (exception $e) { } catch (exception $e) {
@ -98,17 +101,14 @@ final class suspension extends core implements arangodb_document_interface
/** /**
* Generate message about remaining time * Generate message about remaining time
* *
* @param string|null $language Language of the generated text (otherwise used from settings.language) * @param language|null $language Language of the generated text (otherwise used from settings.language)
* @param array &$errors Registry of errors * @param array &$errors Registry of errors
* *
* @return string|null Text: "? days, ? hours and ? minutes" * @return string|null Text: "? days, ? hours and ? minutes"
*/ */
public function message(?string $language = null, array &$errors = []): ?string public function message(?language $language = language::en, array &$errors = []): ?string
{ {
try { try {
// Initializing default value
$language ??= controller::$settings?->language ?? 'en';
// Initializing the time until the suspension ends // Initializing the time until the suspension ends
$difference = date_diff(new datetime('@' . $this->document->end), new datetime()); $difference = date_diff(new datetime('@' . $this->document->end), new datetime());
@ -118,54 +118,54 @@ final class suspension extends core implements arangodb_document_interface
$difference->d, $difference->d,
match ($difference->d > 20 ? $difference->d % 10 : $difference->d % 100) { match ($difference->d > 20 ? $difference->d % 10 : $difference->d % 100) {
1 => match ($language) { 1 => match ($language) {
'ru' => 'день', language::en => 'day',
'en' => 'day', language::ru => 'день',
default => 'day' default => 'day'
}, },
2, 3, 4 => match ($language) { 2, 3, 4 => match ($language) {
'ru' => 'дня', language::en => 'days',
'en' => 'days', language::ru => 'дня',
default => 'days' default => 'days'
}, },
default => match ($language) { default => match ($language) {
'ru' => 'дней', language::en => 'days',
'en' => 'days', language::ru => 'дней',
default => 'days' default => 'days'
} }
}, },
$difference->h, $difference->h,
match ($difference->h > 20 ? $difference->h % 10 : $difference->h % 100) { match ($difference->h > 20 ? $difference->h % 10 : $difference->h % 100) {
1 => match ($language) { 1 => match ($language) {
'ru' => 'час', language::en => 'hours',
'en' => 'hours', language::ru => 'час',
default => 'hour' default => 'hour'
}, },
2, 3, 4 => match ($language) { 2, 3, 4 => match ($language) {
'ru' => 'часа', language::en => 'hours',
'en' => 'hours', language::ru => 'часа',
default => 'hours' default => 'hours'
}, },
default => match ($language) { default => match ($language) {
'ru' => 'часов', language::en => 'hours',
'en' => 'hours', language::ru => 'часов',
default => 'hours' default => 'hours'
} }
}, },
$difference->i, $difference->i,
match ($difference->i > 20 ? $difference->i % 10 : $difference->i % 100) { match ($difference->i > 20 ? $difference->i % 10 : $difference->i % 100) {
1 => match ($language) { 1 => match ($language) {
'ru' => 'минута', language::en => 'minute',
'en' => 'minute', language::ru => 'минута',
default => 'minute' default => 'minute'
}, },
2, 3, 4 => match ($language) { 2, 3, 4 => match ($language) {
'ru' => 'минуты', language::en => 'minutes',
'en' => 'minutes', language::ru => 'минуты',
default => 'minutes' default => 'minutes'
}, },
default => match ($language) { default => match ($language) {
'ru' => 'минут', language::en => 'minutes',
'en' => 'minutes', language::ru => 'минут',
default => 'minutes' default => 'minutes'
} }
} }

View File

@ -410,7 +410,7 @@ final class telegram extends core
$products_deleted, $products_deleted,
$products_old, $products_old,
$products_new, $products_new,
language: 'ru' language: $account->language ?? settings::active()?->language ?? 'en'
); );
// Отправка сообщения // Отправка сообщения
@ -489,7 +489,7 @@ final class telegram extends core
$telegram = $ctx->getEffectiveUser(); $telegram = $ctx->getEffectiveUser();
// Инициализация аккаунта // Инициализация аккаунта
$account = account::initialization($telegram->getId(), $telegram); $account = account::initialize($telegram->getId(), $telegram);
if ($account) { if ($account) {
// Инициализирован аккаунт // Инициализирован аккаунт
@ -561,11 +561,11 @@ final class telegram extends core
} }
// Инициализация сообщения // Инициализация сообщения
$message = "⚠️ *Работа приостановлена*\n*Оставшееся время\:* " . $suspension->message($account->language ?? controller::$settings?->language); $message = "⚠️ *Работа приостановлена*\n*Оставшееся время\:* " . $suspension->message($account->language ?? settings::active()?->language ?? 'en');
// Добавление описания причины приостановки, если найдена // Добавление описания причины приостановки, если найдена
if (!empty($suspension->description)) if (!empty($suspension->description))
$message .= "\n\n" . $suspension->description[$account->language ?? controller::$settings?->language] ?? array_values($suspension->description)[0]; $message .= "\n\n" . $suspension->description[$account->language ?? settings::active()?->language ?? 'en'] ?? array_values($suspension->description)[0];
// Отправка сообщения // Отправка сообщения
$ctx->sendMessage($message) $ctx->sendMessage($message)

View File

@ -49,7 +49,7 @@ trait document
parent::__construct($initialize, $arangodb); parent::__construct($initialize, $arangodb);
// Writing to the property // Writing to the property
if ($document instanceof _document) $this->document = $document; if ($document instanceof _document) $this->__document($document);
else if ($document === null) throw new exception('Failed to initialize an instance of the document from ArangoDB'); else if ($document === null) throw new exception('Failed to initialize an instance of the document from ArangoDB');
} }
@ -98,7 +98,6 @@ trait document
{ {
// Read a property from an instance of the ArangoDB document and exit (success) // Read a property from an instance of the ArangoDB document and exit (success)
return match ($name) { return match ($name) {
'arangodb' => core::$arangodb,
default => $this->document->{$name} default => $this->document->{$name}
}; };
} }

View File

@ -41,8 +41,9 @@ $router
->write('/', 'catalog', 'index', 'GET') ->write('/', 'catalog', 'index', 'GET')
->write('/search', 'catalog', 'search', 'POST') ->write('/search', 'catalog', 'search', 'POST')
->write('/session/connect/telegram', 'session', 'telegram', 'POST') ->write('/session/connect/telegram', 'session', 'telegram', 'POST')
->write('/product/$id', 'catalog', 'product', 'POST') ->write('/category/$identifier', 'catalog', 'index', 'POST')
->write('/$categories...', 'catalog', 'index', 'POST'); ->write('/category', 'catalog', 'index', 'POST')
->write('/product/$identifier', 'catalog', 'product', 'POST');
/* /*

View File

@ -34,16 +34,11 @@ import("/js/core.js").then(() =>
// Write to the core // Write to the core
core.catalog = class catalog { core.catalog = class catalog {
/**
* Current position in hierarchy of the categories
*/
static categories = [];
/** /**
* Registry of filters (instead of cookies) * Registry of filters (instead of cookies)
*/ */
static filters = new Map([ static filters = new Map([
['brand', null] ["brand", null],
]); ]);
/** /**
@ -56,10 +51,12 @@ import("/js/core.js").then(() =>
* @return {void} * @return {void}
*/ */
static category(button, clean = true, force = false) { static category(button, clean = true, force = false) {
// Initialize of the new category name // Initializing identifier of the category
const category = button.getAttribute("data-category-name"); const identifier = button.getAttribute(
"data-category-identifier",
);
this._category(category, clean, force); this._category(identifier, clean, force);
} }
/** /**
@ -80,22 +77,22 @@ import("/js/core.js").then(() =>
/** /**
* Select a category (system) * Select a category (system)
* *
* @param {HTMLElement} button Button of category <a> * @param {string} identifier Identifier of the category
* @param {bool} clean Clear search bar? * @param {bool} clean Clear search bar?
* *
* @return {Promise} Request to the server * @return {Promise} Request to the server
*/ */
static __category(category = "", clean = true) { static __category(category, clean = true) {
if (typeof category === "string") { if (typeof category === "string") {
// // Received required parameters
let urn; return core.request(
if (category === "/" || category === "") urn = "/"; "/category" +
else {urn = this.categories.length > 0 ("/" + category).replace(/^\/*/, "/").trim().replace(
? `/${this.categories.join("/")}/${category}` /\/*$/,
: `/${category}`;} "",
),
return core.request(urn) )
.then((json) => { .then((json) => {
if ( if (
json.errors !== null && json.errors !== null &&
@ -114,11 +111,6 @@ import("/js/core.js").then(() =>
if (search instanceof HTMLElement) search.value = ""; if (search instanceof HTMLElement) search.value = "";
} }
// Write the category to position in the categories hierarchy
if (category !== "/" && category !== "") {
this.categories.push(category);
}
if ( if (
typeof json.title === "string" && typeof json.title === "string" &&
json.title.length > 0 json.title.length > 0
@ -288,9 +280,6 @@ import("/js/core.js").then(() =>
* @todo add animations of errors * @todo add animations of errors
*/ */
static __search(element) { static __search(element) {
// Deinitialization of position in the categories hierarchy
this.categories = [];
return this.__category("/", false) return this.__category("/", false)
.then(function () { .then(function () {
core.request("/search", `text=${element.value}`) core.request("/search", `text=${element.value}`)
@ -392,19 +381,19 @@ import("/js/core.js").then(() =>
/** /**
* Open product card (interface) * Open product card (interface)
* *
* @param {string} id Identifier of a product * @param {string} identifier Identifier of a product
* @param {bool} force Ignore the damper? * @param {bool} force Ignore the damper?
* *
* @return {void} * @return {void}
*/ */
static product(id, force = false) { static product(identifier, force = false) {
this._product(id, force); this._product(identifier, force);
} }
/** /**
* Open product card (damper) * Open product card (damper)
* *
* @param {string} id Identifier of a product * @param {string} identifier Identifier of a product
* @param {bool} force Ignore the damper? * @param {bool} force Ignore the damper?
* *
* @return {void} * @return {void}
@ -418,15 +407,15 @@ import("/js/core.js").then(() =>
/** /**
* Open product card (system) * Open product card (system)
* *
* @param {string} id Identifier of a product * @param {string} identifier Identifier of a product
* *
* @return {Promise} Request to the server * @return {Promise} Request to the server
*/ */
static __product(id) { static __product(identifier) {
if (typeof id === "number") { if (typeof identifier === "number") {
// //
return core.request(`/product/${id}`) return core.request(`/product/${identifier}`)
.then((json) => { .then((json) => {
if ( if (
json.errors !== null && json.errors !== null &&
@ -455,15 +444,18 @@ import("/js/core.js").then(() =>
card.classList.add("card", "unselectable"); card.classList.add("card", "unselectable");
const h3 = document.createElement("h3"); const h3 = document.createElement("h3");
h3.setAttribute("title", json.product.id);
const title = document.createElement("span"); const name = document.createElement("span");
title.classList.add("title"); name.classList.add("name");
title.innerText = json.product.title; name.setAttribute("title", json.product.identifier);
name.innerText = json.product.name;
const brand = document.createElement("small"); const exit = document.createElement("a");
brand.classList.add("brand"); exit.classList.add("exit");
brand.innerText = json.product.brand; exit.setAttribute("type", "button");
const exit_icon = document.createElement("i");
exit_icon.classList.add("icon", "close");
const images = document.createElement("div"); const images = document.createElement("div");
images.classList.add("images", "unselectable"); images.classList.add("images", "unselectable");
@ -528,6 +520,13 @@ import("/js/core.js").then(() =>
images.append(image); images.append(image);
} }
const header = document.createElement("p");
header.classList.add('header');
const brand = document.createElement("small");
brand.classList.add("brand");
brand.innerText = json.product.brand;
const description = document.createElement("p"); const description = document.createElement("p");
description.classList.add("description"); description.classList.add("description");
description.innerText = json.product.description; description.innerText = json.product.description;
@ -542,10 +541,24 @@ import("/js/core.js").then(() =>
const dimensions = document.createElement("small"); const dimensions = document.createElement("small");
dimensions.classList.add("dimensions"); dimensions.classList.add("dimensions");
dimensions.innerText = json.product.dimensions.x +
"x" + const x = json.product.dimensions.x;
json.product.dimensions.y + "x" + const y = json.product.dimensions.y;
json.product.dimensions.z; const z = json.product.dimensions.z;
let formatted = "";
if (x !== '' ) formatted = x;
if (y !== '') {
if (formatted.length === 0) formatted = y;
else formatted += "x" + y;
}
if (z !== '') {
if (formatted.length === 0) formatted = z;
else formatted += "x" + z;
}
dimensions.innerText = formatted;
const weight = document.createElement("small"); const weight = document.createElement("small");
weight.classList.add("weight"); weight.classList.add("weight");
@ -555,10 +568,13 @@ import("/js/core.js").then(() =>
cost.classList.add("cost"); cost.classList.add("cost");
cost.innerText = json.product.cost + "р"; cost.innerText = json.product.cost + "р";
h3.append(title); h3.append(name);
h3.append(brand); exit.append(exit_icon);
h3.append(exit);
card.append(h3); card.append(h3);
card.append(images); card.append(images);
header.append(brand);
card.append(header);
card.append(description); card.append(description);
card.append(compatibility); card.append(compatibility);
footer.append(dimensions); footer.append(dimensions);
@ -581,8 +597,8 @@ import("/js/core.js").then(() =>
); );
history.pushState( history.pushState(
{ product_card: json.product.id }, { product_card: json.product.identifier },
json.product.title, json.product.name,
); );
// блокировка закрытия карточки // блокировка закрытия карточки
@ -595,6 +611,8 @@ import("/js/core.js").then(() =>
wrap.remove(); wrap.remove();
wrap.removeEventListener("mousedown", _from); wrap.removeEventListener("mousedown", _from);
wrap.removeEventListener("touchstart", _from); wrap.removeEventListener("touchstart", _from);
exit.removeEventListener("click", remove);
exit.removeEventListener("touch", remove);
document.removeEventListener("click", close); document.removeEventListener("click", close);
document.removeEventListener("touch", close); document.removeEventListener("touch", close);
window.removeEventListener("popstate", remove); window.removeEventListener("popstate", remove);
@ -615,13 +633,15 @@ import("/js/core.js").then(() =>
from = undefined; from = undefined;
}; };
exit.addEventListener("click", remove);
exit.addEventListener("touch", remove);
document.addEventListener("click", close); document.addEventListener("click", close);
document.addEventListener("touch", close); document.addEventListener("touch", close);
window.addEventListener("popstate", remove); window.addEventListener("popstate", remove);
if (width > card.offsetWidth) { if (width > card.offsetWidth) {
images.hotline = new core.hotline( images.hotline = new core.hotline(
json.product.id, json.product.identfier,
images, images,
); );
images.hotline.step = -0.3; images.hotline.step = -0.3;

View File

@ -8,9 +8,10 @@ main>section[data-section="catalog"] {
} }
main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"] { main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"] {
--padding: 0.7rem;
position: relative; position: relative;
height: 23px; height: 23px;
padding: unset; padding: var(--padding);
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -27,7 +28,9 @@ main>section[data-section="catalog"][data-catalog-type="categories"]:last-child
} }
main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]:has(>img) { main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]:has(>img) {
min-width: calc(50% - var(--padding) * 2);
height: 180px; height: 180px;
padding: unset;
} }
main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]>img { main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]>img {
@ -37,7 +40,8 @@ main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[
width: 110%; width: 110%;
height: 110%; height: 110%;
object-fit: cover; object-fit: cover;
filter: blur(1px); /* filter: blur(1px); */
filter: brightness(60%);
} }
main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]:hover>img { main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]:hover>img {
@ -45,14 +49,12 @@ main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[
} }
main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]:has(>img)>p { main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]:has(>img)>p {
--padding: 0.7rem;
position: absolute; position: absolute;
left: var(--padding); left: var(--padding);
bottom: var(--padding); bottom: var(--padding);
right: var(--padding); right: var(--padding);
margin: unset; margin: unset;
width: min-content; width: min-content;
padding: var(--padding);
border-radius: 0.75rem; border-radius: 0.75rem;
background: var(--tg-theme-secondary-bg-color); background: var(--tg-theme-secondary-bg-color);
} }

View File

@ -0,0 +1,30 @@
@charset "UTF-8";
i.icon.close {
--diameter: 22px;
box-sizing: border-box;
position: relative;
display: block;
width: var(--diameter);
height: var(--diameter);
border: 2px solid transparent;
border-radius: 40px;
}
i.icon.close::after,
i.icon.close::before {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
width: 16px;
height: 2px;
background: currentColor;
transform: rotate(45deg);
border-radius: 5px;
top: 8px;
left: 1px;
}
i.icon.close::after {
transform: rotate(-45deg);
}

View File

@ -108,12 +108,15 @@ search:has(input:disabled) {
backdrop-filter: contrast(0.5); backdrop-filter: contrast(0.5);
} }
*[type="button"] {
cursor: pointer;
}
:is(button, a[type="button"]) { :is(button, a[type="button"]) {
padding: 8px 16px; padding: 8px 16px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
cursor: pointer;
color: var(--tg-theme-button-text-color); color: var(--tg-theme-button-text-color);
background-color: var(--tg-theme-button-color); background-color: var(--tg-theme-button-color);
} }

View File

@ -2,7 +2,7 @@
section#window { section#window {
z-index: 1500; z-index: 1500;
position: absolute; position: fixed;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
display: flex; display: flex;
@ -24,7 +24,7 @@ section#window>div.card {
section#window>div.card>h3 { section#window>div.card>h3 {
margin: 0 0 0.5rem 0; margin: 0 0 0.5rem 0;
height: 23px; height: 23px;
padding: 1rem 1.5rem; padding: 1rem 0;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
@ -32,16 +32,29 @@ section#window>div.card>h3 {
background-color: var(--tg-theme-header-bg-color); background-color: var(--tg-theme-header-bg-color);
} }
section#window>div.card>h3>span.title { section#window>div.card>h3>span.name {
margin-left: 1.5rem;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
section#window>div.card>h3>small.brand { /* section#window>div.card>h3>small.brand {
margin-left: auto; margin-left: auto;
font-size: 0.8rem; font-size: 0.8rem;
font-weight: normal; font-weight: normal;
color: var(--tg-theme-section-header-text-color); color: var(--tg-theme-section-header-text-color);
} */
section#window>div.card>h3>a.exit[type="button"] {
margin-left: auto;
margin-right: 0.5rem;
padding: 0.6rem;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
color: var(--tg-theme-section-header-text-color);
background-color: unset;
} }
section#window>div.card>div.images { section#window>div.card>div.images {

View File

@ -16,7 +16,8 @@ use mirzaev\minimal\controller;
use Twig\Loader\FilesystemLoader, use Twig\Loader\FilesystemLoader,
Twig\Environment as twig, Twig\Environment as twig,
Twig\Extra\Intl\IntlExtension as intl, Twig\Extra\Intl\IntlExtension as intl,
Twig\TwigFilter; Twig\TwigFilter,
Twig\TwigFunction;
// Built-in libraries // Built-in libraries
@ -43,13 +44,17 @@ final class templater extends controller implements ArrayAccess
/** /**
* Constructor of an instance * Constructor of an instance
* *
* @param session|null $session An object implementing a session instance from ArangoDB * @param session|null $session The object implementing a session instance from ArangoDB
* @param account|null $account An object implementing an account instance from ArangoDB * @param account|null $account The object implementing an account instance from ArangoDB
* @param settings|null $settings The object implementing an account instance from ArangoDB
* *
* @return void * @return void
*/ */
public function __construct(?session $session = null, ?account $account = null) public function __construct(
{ ?session $session = null,
?account $account = null,
?settings $settings = null
) {
// Initializing of an instance of twig // Initializing of an instance of twig
$this->twig = new twig(new FilesystemLoader(VIEWS)); $this->twig = new twig(new FilesystemLoader(VIEWS));
@ -57,9 +62,41 @@ final class templater extends controller implements ArrayAccess
$this->twig->addGlobal('theme', 'default'); $this->twig->addGlobal('theme', 'default');
$this->twig->addGlobal('server', $_SERVER); $this->twig->addGlobal('server', $_SERVER);
$this->twig->addGlobal('cookies', $_COOKIE); $this->twig->addGlobal('cookies', $_COOKIE);
$this->twig->addGlobal('settings', settings::active()); $this->twig->addGlobal('settings', $settings);
if (!empty($session?->status())) $this->twig->addGlobal('session', $session); if (!empty($session?->status())) $this->twig->addGlobal('session', $session);
if (!empty($account?->status())) $this->twig->addGlobal('account', $account); if (!empty($account?->status())) $this->twig->addGlobal('account', $account);
$this->twig->addGlobal('language', $account?->language->name ?? $settings?->language->name ?? 'en');
// Initialize function of dimensions formattinx
$this->twig->addFunction(
new TwigFunction(
'format_dimensions',
function (
string|int|float|null $x = null,
string|int|float|null $y = null,
string|int|float|null $z = null,
string|null $before = null,
string|null $after = null
) {
// Initialzing the buffer of result
$result = '';
// Generating
if (!empty($x)) $result .= $x;
if (!empty($y))
if (empty($result)) $result = "$y";
else $result .= "x$y";
if (!empty($z))
if (empty($result)) $result = "$z";
else $result .= "x$z";
if (!empty($result)) $result = "$before$result$after";
// Exit (success)
return $result;
}
)
);
// Initializing of twig extensions // Initializing of twig extensions
$this->twig->addExtension(new intl()); $this->twig->addExtension(new intl());

View File

@ -2,11 +2,19 @@
<section class="unselectable" data-section="catalog" data-catalog-type="categories" <section class="unselectable" data-section="catalog" data-catalog-type="categories"
data-catalog-level="{{ level ?? 0 }}"> data-catalog-level="{{ level ?? 0 }}">
{% for category in categories %} {% for category in categories %}
{% if category.images %}
<a id="{{ category.getId() }}" class="category" type="button" onclick="return core.catalog.category(this);" <a id="{{ category.getId() }}" class="category" type="button" onclick="return core.catalog.category(this);"
data-category-identifier="{{ category.identifier }}"> data-category-identifier="{{ category.identifier }}">
<img src="{{ category.images.0.storage }}" alt="{{ category.name[ account.language ?? settings.language ?? 'en' ] }}" ondrugstart="return false;"> <img src="{{ category.images.0.storage }}"
<p>{{ category.name[ account.language ?? settings.language ?? 'en' ] }}</p> alt="{{ category.name[language] }}" ondrugstart="return false;">
<p>{{ category.name[language] }}</p>
</a> </a>
{% else %}
<a id="{{ category.getId() }}" class="category" type="button" onclick="return core.catalog.category(this);"
data-category-identifier="{{ category.identifier }}">
<p>{{ category.name[language] }}</p>
</a>
{% endif %}
{% endfor %} {% endfor %}
</section> </section>
{% endif %} {% endif %}

View File

@ -1,10 +1,9 @@
{% macro card(product) %} {% macro card(product) %}
{% set title = product.title.ru ~ ' ' ~ product.brand.ru ~ ' ' ~ product.dimensions.x ~ 'x' ~ product.dimensions.y ~ 'x' {% set title = product.name[language] ~ ' ' ~ product.brand[language] ~ format_dimensions(product.dimensions.x, product.dimensions.y, product.dimensions.z, ' ') ~ ' ' ~ product.weight ~ 'г' %}
~ product.dimensions.z ~ ' ' ~ product.weight ~ 'г' %}
<article id="{{ product.getId() }}" class="product unselectable"> <article id="{{ product.getId() }}" class="product unselectable">
<a onclick="core.catalog.product({{ product.getKey() }})"> <a onclick="core.catalog.product({{ product.identifier }})">
<img src="{{ product.images.0.storage }}" alt="{{ product.title.ru }}" ondrugstart="return false;"> <img src="{{ product.images.0.storage }}" alt="{{ product.name[language] }}" ondrugstart="return false;">
<p class="title" title="{{ product.title.ru }}"> <p class="title" title="{{ product.name[language] }}">
{{ title | length > 45 ? title | slice(0, 45) ~ '...' : title }} {{ title | length > 45 ? title | slice(0, 45) ~ '...' : title }}
</p> </p>
</a> </a>
@ -13,7 +12,6 @@
</button> </button>
</article> </article>
{% endmacro %} {% endmacro %}
{% if products is not empty %} {% if products is not empty %}
<section class="unselectable" data-section="catalog" data-catalog-type="products" data-catalog-level="{{ level ?? 0 }}"> <section class="unselectable" data-section="catalog" data-catalog-type="products" data-catalog-level="{{ level ?? 0 }}">
<div class="column"> <div class="column">

View File

@ -5,6 +5,7 @@
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/catalog/2columns.css" /> <link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/catalog/2columns.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/search.css" /> <link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/search.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/shopping_cart.css" /> <link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/shopping_cart.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/close.css" />
{% endblock %} {% endblock %}
{% block main %} {% block main %}