diff --git a/README.md b/README.md index 182ee50..5609ac4 100755 --- a/README.md +++ b/README.md @@ -6,39 +6,36 @@ Basis for developing chat-robots with "Web App" technology for Telegram ### AnangoDB -1. Create a View in ArangoDB for the document "product" -` -"links": { - "product": { - "fields": { - "description": { - "analyzers": [ - "text_ru" - ] - } - } - } -} -` - -2. Create a Graph with the specified values -**Name:** hierarchy +1. Create a Graph with the specified values +**Name:** catalog **edgeDefinition:** entry -**fromCollections:** part, product... -**toCollections:** category, part... +**fromCollections:** categoy, product +**toCollections:** category + +2. Create a Graph with the specified values +**Name:** sessions + +**edgeDefinition:** connect +**fromCollections:** account +**toCollections:** session 3. Create indexes for the "product" collection **Type:** "Inverted Index" -**Fields:** title.RU +**Fields:** name.ru **Analyzer:** "text_ru" **Search field:** true -**Name:** title_ru +**Name:** name_ru +*Add indexes for all search parameters and for all languages (search language is selected based on the user's language, +otherwise from the default language specified in the active settings from **settings** collection document)* + +*See fields in the `mirzaev/arming_bot/models/product` +**name.ru**, **description.ru** and **compatibility.ru*** + 4. Create a View with the specified values -**Name:** products_search - **type:** search-alias (you can also use "arangosearch") +**name:** **product**s_search **indexes:** ` "indexes": [ @@ -51,19 +48,33 @@ Basis for developing chat-robots with "Web App" technology for Telegram ### NGINX -1. Add this to a NGINX config: `try_files $uri $uri/ /index.php;` - -2. Add this to a NGINX config +1. Example of NGINX server file ` -location /images { - alias /PATH/TO/public/themes/default/images; +location / { + try_files $uri $uri/ /index.php; +} + +location ~ /(?categories|products) { + root /var/www/arming_bot/mirzaev/arming_bot/system/storage; + try_files $uri =404; +} + +location ~ \.php$ { + ... } ` +### SystemD (or any alternative you like) + +1. Execute: `sudo cp telegram-huesos.service /etc/systemd/system/telegram-huesos.service` + +*before you execute the command think about **what it does** and whether the **paths** are specified correctly* +*the configuration file is very simple and you can remake it for any alternative to SystemD that you like* + ## Settings Settings of chat-robot and Web App -Make sure you have a "settings" collection (can be created automatically) and at least one document with the "status" parameter set to "active" +Make sure you have a **settings** collection (can be created automatically) and at least one document with the "status" parameter set to "active" ` { "status": "active" @@ -78,7 +89,7 @@ Language for system messages if user language could not be determined ## Suspensions System of suspensions of chat-robot and Web App -Make sure you have a "suspension" collection (can be created automatically) +Make sure you have a **suspension** collection (can be created automatically) ` { "end": 1726068961, diff --git a/composer.json b/composer.json index 2eba5e3..4b1ff7f 100755 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "twig/twig": "^3.10", "twig/extra-bundle": "^3.7", "twig/intl-extra": "^3.10", - "phpoffice/phpspreadsheet": "^2.1" + "avadim/fast-excel-reader": "^2.19" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 9d4df12..78efa9e 100755 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,120 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "61697600b2677e22a6133cff9768d9eb", + "content-hash": "e580e133abf1c4a60898f255e006a3e9", "packages": [ + { + "name": "avadim/fast-excel-helper", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/aVadim483/fast-excel-helper.git", + "reference": "2792c4a20b529b537ede7148e2c29dc4677818db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aVadim483/fast-excel-helper/zipball/2792c4a20b529b537ede7148e2c29dc4677818db", + "reference": "2792c4a20b529b537ede7148e2c29dc4677818db", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-zip": "*", + "php": ">=7.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "avadim\\FastExcelHelper\\": "./src/FastExcelHelper" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Helper for avadim/fast-excel-reader and avadim/fast-excel-writer", + "homepage": "https://github.com/aVadim483/fast-excel-helper", + "keywords": [ + "MS Office", + "PHPExcel", + "excel", + "library", + "office 2007", + "parser", + "php", + "reader", + "spreadsheet", + "writer", + "xlsx" + ], + "support": { + "issues": "https://github.com/aVadim483/fast-excel-helper/issues", + "source": "https://github.com/aVadim483/fast-excel-helper/tree/v1.2.1" + }, + "time": "2024-09-06T20:37:07+00:00" + }, + { + "name": "avadim/fast-excel-reader", + "version": "v2.19.0", + "source": { + "type": "git", + "url": "https://github.com/aVadim483/fast-excel-reader.git", + "reference": "e2fa125a93c0c5115344ebab1a36b81cd2be63c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aVadim483/fast-excel-reader/zipball/e2fa125a93c0c5115344ebab1a36b81cd2be63c8", + "reference": "e2fa125a93c0c5115344ebab1a36b81cd2be63c8", + "shasum": "" + }, + "require": { + "avadim/fast-excel-helper": "^1.1", + "ext-ctype": "*", + "ext-mbstring": "*", + "ext-xmlreader": "*", + "ext-zip": "*", + "php": ">=7.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "avadim\\FastExcelReader\\": "./src/FastExcelReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Lightweight and very fast XLSX Excel Spreadsheet Reader in PHP", + "homepage": "https://github.com/aVadim483/fast-excel-reader", + "keywords": [ + "MS Office", + "PHPExcel", + "excel", + "import", + "library", + "office 2007", + "parser", + "php", + "reader", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/aVadim483/fast-excel-reader/issues", + "source": "https://github.com/aVadim483/fast-excel-reader/tree/v2.19.0" + }, + "time": "2024-09-22T19:34:27+00:00" + }, { "name": "badfarm/zanzara", "version": "0.9.1", @@ -584,194 +696,6 @@ }, "time": "2024-08-02T07:48:17+00:00" }, - { - "name": "maennchen/zipstream-php", - "version": "3.1.0", - "source": { - "type": "git", - "url": "https://github.com/maennchen/ZipStream-PHP.git", - "reference": "b8174494eda667f7d13876b4a7bfef0f62a7c0d1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/b8174494eda667f7d13876b4a7bfef0f62a7c0d1", - "reference": "b8174494eda667f7d13876b4a7bfef0f62a7c0d1", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "ext-zlib": "*", - "php-64bit": "^8.1" - }, - "require-dev": { - "ext-zip": "*", - "friendsofphp/php-cs-fixer": "^3.16", - "guzzlehttp/guzzle": "^7.5", - "mikey179/vfsstream": "^1.6", - "php-coveralls/php-coveralls": "^2.5", - "phpunit/phpunit": "^10.0", - "vimeo/psalm": "^5.0" - }, - "suggest": { - "guzzlehttp/psr7": "^2.4", - "psr/http-message": "^2.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "ZipStream\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paul Duncan", - "email": "pabs@pablotron.org" - }, - { - "name": "Jonatan Männchen", - "email": "jonatan@maennchen.ch" - }, - { - "name": "Jesse Donat", - "email": "donatj@gmail.com" - }, - { - "name": "András Kolesár", - "email": "kolesar@kolesar.hu" - } - ], - "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", - "keywords": [ - "stream", - "zip" - ], - "support": { - "issues": "https://github.com/maennchen/ZipStream-PHP/issues", - "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.0" - }, - "funding": [ - { - "url": "https://github.com/maennchen", - "type": "github" - }, - { - "url": "https://opencollective.com/zipstream", - "type": "open_collective" - } - ], - "time": "2023-06-21T14:59:35+00:00" - }, - { - "name": "markbaker/complex", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/MarkBaker/PHPComplex.git", - "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", - "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "dev-master", - "phpcompatibility/php-compatibility": "^9.3", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "squizlabs/php_codesniffer": "^3.7" - }, - "type": "library", - "autoload": { - "psr-4": { - "Complex\\": "classes/src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mark Baker", - "email": "mark@lange.demon.co.uk" - } - ], - "description": "PHP Class for working with complex numbers", - "homepage": "https://github.com/MarkBaker/PHPComplex", - "keywords": [ - "complex", - "mathematics" - ], - "support": { - "issues": "https://github.com/MarkBaker/PHPComplex/issues", - "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" - }, - "time": "2022-12-06T16:21:08+00:00" - }, - { - "name": "markbaker/matrix", - "version": "3.0.1", - "source": { - "type": "git", - "url": "https://github.com/MarkBaker/PHPMatrix.git", - "reference": "728434227fe21be27ff6d86621a1b13107a2562c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", - "reference": "728434227fe21be27ff6d86621a1b13107a2562c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "dev-master", - "phpcompatibility/php-compatibility": "^9.3", - "phpdocumentor/phpdocumentor": "2.*", - "phploc/phploc": "^4.0", - "phpmd/phpmd": "2.*", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "sebastian/phpcpd": "^4.0", - "squizlabs/php_codesniffer": "^3.7" - }, - "type": "library", - "autoload": { - "psr-4": { - "Matrix\\": "classes/src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mark Baker", - "email": "mark@demon-angel.eu" - } - ], - "description": "PHP Class for working with matrices", - "homepage": "https://github.com/MarkBaker/PHPMatrix", - "keywords": [ - "mathematics", - "matrix", - "vector" - ], - "support": { - "issues": "https://github.com/MarkBaker/PHPMatrix/issues", - "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" - }, - "time": "2022-12-02T22:17:43+00:00" - }, { "name": "mirzaev/arangodb", "version": "1.3.0", @@ -1417,110 +1341,6 @@ }, "time": "2024-09-04T13:22:54+00:00" }, - { - "name": "phpoffice/phpspreadsheet", - "version": "2.2.2", - "source": { - "type": "git", - "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", - "reference": "ffbcee68069b073bff07a71eb321dcd9f2763513" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/ffbcee68069b073bff07a71eb321dcd9f2763513", - "reference": "ffbcee68069b073bff07a71eb321dcd9f2763513", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-dom": "*", - "ext-fileinfo": "*", - "ext-gd": "*", - "ext-iconv": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-simplexml": "*", - "ext-xml": "*", - "ext-xmlreader": "*", - "ext-xmlwriter": "*", - "ext-zip": "*", - "ext-zlib": "*", - "maennchen/zipstream-php": "^2.1 || ^3.0", - "markbaker/complex": "^3.0", - "markbaker/matrix": "^3.0", - "php": "^8.1", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", - "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "dev-main", - "dompdf/dompdf": "^2.0 || ^3.0", - "friendsofphp/php-cs-fixer": "^3.2", - "mitoteam/jpgraph": "^10.3", - "mpdf/mpdf": "^8.1.1", - "phpcompatibility/php-compatibility": "^9.3", - "phpstan/phpstan": "^1.1", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.6 || ^10.5", - "squizlabs/php_codesniffer": "^3.7", - "tecnickcom/tcpdf": "^6.5" - }, - "suggest": { - "dompdf/dompdf": "Option for rendering PDF with PDF Writer", - "ext-intl": "PHP Internationalization Functions", - "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", - "mpdf/mpdf": "Option for rendering PDF with PDF Writer", - "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Maarten Balliauw", - "homepage": "https://blog.maartenballiauw.be" - }, - { - "name": "Mark Baker", - "homepage": "https://markbakeruk.net" - }, - { - "name": "Franck Lefevre", - "homepage": "https://rootslabs.net" - }, - { - "name": "Erik Tilt" - }, - { - "name": "Adrien Crivelli" - } - ], - "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", - "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", - "keywords": [ - "OpenXML", - "excel", - "gnumeric", - "ods", - "php", - "spreadsheet", - "xls", - "xlsx" - ], - "support": { - "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", - "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/2.2.2" - }, - "time": "2024-08-08T02:31:26+00:00" - }, { "name": "psr/cache", "version": "3.0.0", @@ -1668,58 +1488,6 @@ }, "time": "2019-01-08T18:20:26+00:00" }, - { - "name": "psr/http-client", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-client.git", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", - "shasum": "" - }, - "require": { - "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0 || ^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Client\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP clients", - "homepage": "https://github.com/php-fig/http-client", - "keywords": [ - "http", - "http-client", - "psr", - "psr-18" - ], - "support": { - "source": "https://github.com/php-fig/http-client" - }, - "time": "2023-09-23T14:17:50+00:00" - }, { "name": "psr/http-factory", "version": "1.1.0", @@ -1878,57 +1646,6 @@ }, "time": "2021-05-03T11:20:27+00:00" }, - { - "name": "psr/simple-cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/simple-cache.git", - "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", - "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\SimpleCache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interfaces for simple caching", - "keywords": [ - "cache", - "caching", - "psr", - "psr-16", - "simple-cache" - ], - "support": { - "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" - }, - "time": "2021-10-29T13:26:27+00:00" - }, { "name": "react/cache", "version": "v1.2.0", diff --git a/mirzaev/arming_bot/system/controllers/catalog.php b/mirzaev/arming_bot/system/controllers/catalog.php index 34cfce4..25f0471 100755 --- a/mirzaev/arming_bot/system/controllers/catalog.php +++ b/mirzaev/arming_bot/system/controllers/catalog.php @@ -6,7 +6,8 @@ namespace mirzaev\arming_bot\controllers; // Files of the project use mirzaev\arming_bot\controllers\core, - mirzaev\arming_bot\models\categories, + mirzaev\arming_bot\models\catalog as model, + mirzaev\arming_bot\models\entry, mirzaev\arming_bot\models\category, mirzaev\arming_bot\models\product; @@ -34,54 +35,43 @@ final class catalog extends core */ public function index(array $parameters = []): ?string { - if (!empty($parameters['categories']) && $parameters['categories'] !== ['/']) { - // Переданы категории ["category1", "category2", "category3"] (иерархия) + if (!empty($parameters['category'])) { + // Передана категория (идентификатор) // Инициализация актуальной категории - $category = end($parameters['categories']); + $category = new category(document: category::_read('d.identifier == @identifier', parameters: ['identifier' => $parameters['category']])); - if ($model = categories::category($category)) { - // Найдена модель обработки актуальной категории + if ($category instanceof category) { + // Found the category - if (method_exists($model, 'entries')) { - // Найден метод поиска вхождений + // Поиск категорий или товаров входящих в актуальную категорию + $entries = entry::search( + document: $category, + amount: 30, + errors: $this->errors['catalog'] + ); - // Поиск категорий или товаров входящих в актуальную категорию - $entries = $model::entries( - category: $model->getId(), - filter: 'v.deleted != true && v.hidden != true', - amount: 30, - errors: $this->errors['catalog'] - ); + // Объявление буферов категорий и товаров (важно - в единственном числе, по параметру из базы данных) + $category = $product = []; - // Объявление буферов категорий и товаров (важно - в единственном числе, по параметру из базы данных) - $category = $product = []; + foreach ($entries as $entry) { + // Перебор вхождений - foreach ($entries as $entry) { - // Перебор вхождений - - // Запись массивов категорий и товаров ($category и $product) в буфер глобальной переменной шаблонизатора - ${$entry->_type}[] = $entry; - } - - // Запись категорий из буфера в глобальную переменную шаблонизатора - $this->view->categories = $category; - - // Запись товаров из буфера в глобальную переменную шаблонизатора - $this->view->products = $product; + // Запись массивов категорий и товаров ($category и $product) в буфер глобальной переменной шаблонизатора + ${$entry->_type}[] = $entry; } + + // Запись категорий из буфера в глобальную переменную шаблонизатора + $this->view->categories = $category; + + // Запись товаров из буфера в глобальную переменную шаблонизатора + $this->view->products = $product; } } else { - // Не переданы категории + // Не передана категория - // Поиск категорий: "categories" - // @todo сделать автоматический поиск "самой верхней" категории - $this->view->categories = category::_read( - filter: 'd.deleted != true && d.hidden != true', - sort: 'd.position ASC, d.name ASC, d.created DESC', - amount: 30, - errors: $this->errors['catalog'] - ); + // Поиск категорий: "categories" (самый верхний уровень) + $this->view->categories = entry::ascendants(descendant: new category, errors: $this->errors['catalog']); // Search for products /* $this->view->products = product::read( @@ -92,6 +82,11 @@ final class catalog extends core ); */ } + // Generating filters + $this->view->filters = [ + 'brands' => product::collect('d.brand.ru', $this->errors['catalog']) + ]; + if ($_SERVER['REQUEST_METHOD'] === 'GET') { // GET request @@ -114,7 +109,8 @@ final class catalog extends core 'title' => $title ?? '', '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') ], 'errors' => $this->errors ] diff --git a/mirzaev/arming_bot/system/controllers/core.php b/mirzaev/arming_bot/system/controllers/core.php index 96b245f..cb8e006 100755 --- a/mirzaev/arming_bot/system/controllers/core.php +++ b/mirzaev/arming_bot/system/controllers/core.php @@ -12,9 +12,6 @@ use mirzaev\arming_bot\views\templater, mirzaev\arming_bot\models\settings, mirzaev\arming_bot\models\suspension; -// Library for ArangoDB -use ArangoDBClient\Document as _document; - // Framework for PHP use mirzaev\minimal\controller; diff --git a/mirzaev/arming_bot/system/controllers/index.php b/mirzaev/arming_bot/system/controllers/index.php index 8313b4c..ae1bf2b 100755 --- a/mirzaev/arming_bot/system/controllers/index.php +++ b/mirzaev/arming_bot/system/controllers/index.php @@ -16,6 +16,7 @@ use mirzaev\arming_bot\controllers\core, */ final class index extends core { + /** * Render the main page * @@ -23,11 +24,8 @@ final class index extends core */ public function index(array $parameters = []): ?string { - // Поиск товаров - /* $this->view->products = product::read(); */ - // Exit (success) - if ($_SERVER['REQUEST_METHOD'] === 'GET') return $this->view->render('catalog.html'); + if ($_SERVER['REQUEST_METHOD'] === 'GET') return $this->view->render('index.html'); // Exit (fail) return null; diff --git a/mirzaev/arming_bot/system/models/account.php b/mirzaev/arming_bot/system/models/account.php index f83fc6e..47d777c 100755 --- a/mirzaev/arming_bot/system/models/account.php +++ b/mirzaev/arming_bot/system/models/account.php @@ -14,9 +14,6 @@ use mirzaev\arming_bot\models\core, use mirzaev\arangodb\collection, mirzaev\arangodb\document; -// Library для ArangoDB -use ArangoDBClient\Document as _document; - // Framework for Telegram use Zanzara\Telegram\Type\User as telegram; @@ -24,9 +21,11 @@ use Zanzara\Telegram\Type\User as telegram; use exception; /** - * Model of an account + * Model of account * * @package mirzaev\arming_bot\models + * + * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License * @author Arsen Mirzaev Tatyano-Muradovich */ final class account extends core implements arangodb_document_interface @@ -39,77 +38,90 @@ final class account extends core implements arangodb_document_interface final public const string COLLECTION = 'account'; /** - * Инициализация + * Initialize * - * @param int $id Идентификатор Telegram + * @param int $identifier Identifier of the account * @param telegram|array|null $registration Данные для регистрация, если аккаунт не найден * @param array &$errors Registry of errors * * @return static|null Объект аккаунта, если найден */ - public static function initialization(int $id, telegram|array|null $registration = null, array &$errors = []): static|null + public static function initialize(int $identifier, telegram|array|null $registration = null, array &$errors = []): static|null { try { - if (collection::init(core::$arangodb->session, self::COLLECTION)) { - if ($document = collection::search(core::$arangodb->session, sprintf("FOR d IN %s FILTER d.id == %u RETURN d", self::COLLECTION, $id))) { - // Найден аккаунт + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + // Initialized the collection - // Инициализация объекта аккаунта - $account = new static; - - // Запись инстанции документа в объект - $account->document = $document; - - // Возврат (успех) - return $account; - } else if ($registration) { - // Не найден аккаунт и запрошена его регистрация - - // Создание аккаунта - document::write( - core::$arangodb->session, - self::COLLECTION, - (is_array($registration) - ? $registration : + try { + // Initializing the account and exit (success) + return new static( + document: collection::execute( + <<<'AQL' + FOR d IN @@collection + FILTER d.identifier == @identifier + RETURN d + AQL, [ - 'id' => $registration->getId(), - 'name' => [ - 'first' => $registration->getFirstName(), - 'last' => $registration->getLastName() - ], - 'domain' => $registration->getUsername(), - 'robot' => $registration->isBot(), - 'banned' => false, - 'tester' => false, - 'developer' => false, - 'access' => [ - 'settings' => false - ], - 'menus' => [ - 'attachments' => $registration->getAddedToAttachmentMenu() - ], - 'messages' => true, - 'groups' => [ - 'join' => $registration->getCanJoinGroups(), - 'messages' => $registration->getCanReadAllGroupMessages() - ], - 'premium' => $registration->isPremium(), - 'language' => $registration->getLanguageCode(), - 'queries' => [ - 'inline' => $registration->getSupportsInlineQueries() - ] - ]) + [ - 'version' => ROBOT_VERSION, - 'active' => true - ] + '@collection' => static::COLLECTION, + 'identifier' => $identifier + ], + errors: $errors + ) ); + } catch (exception $e) { + if ($registration) { + // Not found the account and registration is requested - // Инициализация (без регистрации) - return static::initialization($id, errors: $errors); - } else throw new exception('Account not found'); - } else throw new exception('Failed to initialize document collection: ' . self::COLLECTION); + // Creating account + $account = document::write( + static::COLLECTION, + (is_array($registration) + ? $registration : + [ + 'identifier' => $registration->getId(), + 'name' => [ + 'first' => $registration->getFirstName(), + 'last' => $registration->getLastName() + ], + 'domain' => $registration->getUsername(), + 'robot' => $registration->isBot(), + 'banned' => false, + 'tester' => false, + 'developer' => false, + 'access' => [ + 'settings' => false + ], + 'menus' => [ + 'attachments' => $registration->getAddedToAttachmentMenu() + ], + 'messages' => true, + 'groups' => [ + 'join' => $registration->getCanJoinGroups(), + 'messages' => $registration->getCanReadAllGroupMessages() + ], + 'premium' => $registration->isPremium(), + 'language' => $registration->getLanguageCode(), + 'queries' => [ + 'inline' => $registration->getSupportsInlineQueries() + ] + ]) + [ + 'version' => ROBOT_VERSION, + 'active' => true + ], + errors: $errors + ); + + if ($account) { + // Created account + + // Initializing of the account (without registration request) + return static::initialize($identifier, errors: $errors); + } else throw new exception('Failed to register account'); + } else throw new exception('Failed to find account'); + } + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); } catch (exception $e) { - // Write to the registry of errors + // Writing to the registry of errors $errors[] = [ 'text' => $e->getMessage(), 'file' => $e->getFile(), @@ -118,7 +130,7 @@ final class account extends core implements arangodb_document_interface ]; } - // Выход (провал) + // Exit (fail) return null; } } diff --git a/mirzaev/arming_bot/system/models/catalog.php b/mirzaev/arming_bot/system/models/catalog.php new file mode 100755 index 0000000..8fbd210 --- /dev/null +++ b/mirzaev/arming_bot/system/models/catalog.php @@ -0,0 +1,487 @@ + + */ +final class catalog extends core +{ + use yandex, files { + yandex::download as yandex; + } + + /** + * Collect parameter from all products + * + * @param string $documment Path to the EXCEL-document + * @param int &$categories_loaded Counter of loaded categories + * @param int &$categories_created Counter of created categories + * @param int &$categories_updated Counter of updated categories + * @param int &$categories_deleted Counter of deleted categories + * @param int &$categories_new Counter of new categories + * @param int &$categories_old Counter of old categories + * @param int &$products_loaded Counter of loaded products + * @param int &$products_created Counter of created products + * @param int &$products_updated Counter of updated products + * @param int &$products_deleted Counter of deleted products + * @param int &$products_new Counter of new products + * @param int &$products_old Counter of old products + * @param string &$language Language (en, ru...) + * @param array &$errors Registry of errors + * + * @return void + * + * @todo + * 1. Сначала создать все категории и затем снова по циклу пройтись уже создавать entry между ними + * 2. Сжимать изображения + */ + public static function import( + string $document, + int &$categories_loaded = 0, + int &$categories_created = 0, + int &$categories_updated = 0, + int &$categories_deleted = 0, + int &$categories_old = 0, + int &$categories_new = 0, + int &$products_loaded = 0, + int &$products_created = 0, + int &$products_updated = 0, + int &$products_deleted = 0, + int &$products_old = 0, + int &$products_new = 0, + string $language = 'en', + array &$errors = [] + ): void { + try { + // Initializing the spreadsheet + $spreadsheet = excel::open($document); + + // Inititalizing worksheets + $categories = $spreadsheet->getSheet('Категории'); + $products = $spreadsheet->getSheet('Товары'); + + // Counting old documents + $categories_old = collection::count(category::COLLECTION, errors: $errors); + $products_old = collection::count(product::COLLECTION, errors: $errors); + + // Initializing the buffer of handler categories and products + $handled = [ + 'categories' => [], + 'products' => [] + ]; + + foreach ( + $categories->nextRow( + [ + 'A' => 'identifier', + 'B' => 'name', + 'C' => 'category', + 'D' => 'images', + 'E' => 'position' + ], + excel::KEYS_FIRST_ROW + ) as $number => $row + ) { + // Iterate over categories + + try { + if (!empty($row['identifier']) && !empty($row['name'])) { + // All required cells are filled in + + // Incrementing the counter of loaded categories + ++$categories_loaded; + + // Declaring the variable with the status that a new category has been created + $created = false; + + // Declaring the variable with the category + $category = null; + + try { + // Initializing the category + $category = new category(document: category::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $row['identifier']], errors: $errors)[0] ?? null); + + // Initializing name of the category + $category->name[$language] === $row['name'] || $category->name = [[$language => $row['name']]] + $category->name ?? []; + + // Initializing position of the category + $category->position === $row['position'] || $category->position = $row['position']; + } catch (exception $e) { + // Not found the category + + // Creating the category + $_id = category::write((int) $row['identifier'], [$language => $row['name']], $row['position'] ?? null, $errors); + + // Initializing the category + $category = new category(document: $created = category::_read('d._id == @_id', parameters: ['_id' => $_id], errors: $errors)[0] ?? null); + + // Incrementing the counter of created categories + if ($created) ++$categories_created; + }; + + if ($category instanceof category) { + // Found the category + + if (!empty($row['category'])) { + // Received 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); + + if ($ascendant instanceof category) { + // Found the ascendant category + + // Deleting entries of the category in ArangoDB + entry::banish($category, $errors); + + // Writing the category as an entry to the ascendant category in ArangoDB + entry::write($category, $ascendant, $errors); + } + } + + if (!empty($row['images'])) { + // Received images + + // Initializing new images of the category + $images = explode(' ', trim($row['images'])); + + // Reinitialize images? (true, if no images found or their amount does not match) + $reinitialize = !$category->images || count($category->images) !== count($images); + + // Checking the identity of existing images with new images (if reinitialization of images has not yet been requested) + if (!$reinitialize) foreach ($category->images as $key => $image) if ($reinitialize = $image['source'] !== $images[$key]) break; + + if ($reinitialize) { + // Requested reinitialization of images + + // Initializing the buffer of images + $buffer = []; + + foreach ($images as $index => $image) { + // Iterating over new images + + // Skipping empty URI`s + if (empty($image = trim($image))) continue; + + // Initializing path to directory of the image in storage + $directory = DIRECTORY_SEPARATOR . 'categories' . DIRECTORY_SEPARATOR . $row['identifier']; + + // Initializing URL of the image in storage + $url = STORAGE . $directory; + + // Initializing URN of the image in storage + $urn = $index . '.jpg'; + + // Initializing URI of the image in storage + $uri = $url . DIRECTORY_SEPARATOR . $urn; + + // Initializing the directory in storage + if (!file_exists($url)) mkdir($url, 0775, true); + + if (static::yandex($image, $uri, errors: $errors)) { + // The image is downloaded + + // Writing the image to the buffer if images + $buffer[] = [ + 'source' => $image, + 'storage' => $directory . DIRECTORY_SEPARATOR . $urn + ]; + } + } + + // Initializing images of the category + $category->images = $buffer; + } + } + + // Writing in ArangoDB + $updated = document::update($category->__document(), errors: $errors); + + // Incrementing the counter of updated categories + if ($updated && !$created) ++$categories_updated; + } else throw new exception("Failed to initialize category: {$row['name']} ($number)"); + } + + // Writing to the registry of handled categories and products + $handled['categories'][] = $row['identifier']; + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + } + + foreach ( + $products->nextRow( + [ + 'A' => 'identifier', + 'B' => 'name', + 'C' => 'category', + 'D' => 'description', + 'E' => 'cost', + 'F' => 'weight', + 'G' => 'x', + 'H' => 'y', + 'I' => 'z', + 'J' => 'brand', + 'K' => 'compatibility', + 'L' => 'images', + 'M' => 'position' + ], + excel::KEYS_FIRST_ROW + ) as $number => $row + ) { + // Iterate over products + + try { + if (!empty($row['identifier']) && !empty($row['name'])) { + // All required cells are filled in + + // Incrementing the counter of loaded products + ++$products_loaded; + + // Declaring the variable with the status that a new product has been created + $created = false; + + // Declaring the variable with the product + $product = null; + + try { + // Initializing the product + $product = new product(document: product::_read('d.identifier == %u', parameters: ['identifier' => (int) $row['identifier']], errors: $errors)[0] ?? null); + + // Initializing name of the category + $product->name[$language] === $row['name'] || $product->name = [[$language => $row['name']]] + $product->name ?? []; + + // Initializing position of the product + $product->position === $row['position'] || $product->position = $row['position']; + } catch (exception $e) { + // Not found the product + + // Creating the product + $_id = product::write( + (int) $row['identifier'], + [$language => $row['name']], + [$language => $row['description']], + (float) $row['cost'], + (float) $row['weight'], + ['x' => $row['x'], 'y' => $row['y'], 'z' => $row['z']], + $row['position'] ?? null, + errors: $errors + ); + + // Initializing the product + $product = new product(document: $created = product::_read(sprintf('d._id == "%s"', $_id), errors: $errors)[0] ?? null); + + // Incrementing the counter of created products + if ($created) ++$products_created; + } + + if ($product instanceof product) { + // Found the product + + if (!empty($row['category'])) { + // Received the category + + // Initializing the category + $category = new category(document: category::_read(sprintf('d.identifier == %u', (int) $row['category']), errors: $errors)[0] ?? null); + + if ($category instanceof category) { + // Found the ascendant category + + // Deleting entries of the product in ArangoDB + entry::banish($product, $errors); + + // Writing the product as an entry to the ascendant category in ArangoDB + entry::write($product, $category, $errors); + } + } + + if (!empty($row['images'])) { + // Received images + + // Initializing new images of the category + $images = explode(' ', trim($row['images'])); + + // Reinitialize images? (true, if no images found or their amount does not match) + $reinitialize = !$product->images || count($product->images) !== count($images); + + // Checking the identity of existing images with new images (if reinitialization of images has not yet been requested) + if (!$reinitialize) foreach ($product->images as $key => $image) if ($reinitialize = $image['source'] !== $images[$key]) break; + + if ($reinitialize) { + // Requested reinitialization of images + + // Initializing the buffer of images + $buffer = []; + + foreach ($images as $index => $image) { + // Iterating over new images + + // Skipping empty URI`s + if (empty($image = trim($image))) continue; + + // Initializing path to directory of the image in storage + $directory = DIRECTORY_SEPARATOR . 'products' . DIRECTORY_SEPARATOR . $row['identifier']; + + // Initializing URL of the image in storage + $url = STORAGE . $directory; + + // Initializing URN of the image in storage + $urn = $index . '.jpg'; + + // Initializing URI of the image in storage + $uri = $url . DIRECTORY_SEPARATOR . $urn; + + // Initializing the directory in storage + if (!file_exists($url)) mkdir($url, 0775, true); + + if (static::yandex($image, $uri, errors: $errors)) { + // The image is downloaded + + // Writing the image to the buffer if images + $buffer[] = [ + 'source' => $image, + 'storage' => $directory . DIRECTORY_SEPARATOR . $urn + ]; + } + } + + // Initializing images of the category + $product->images = $buffer; + } + } + + // Writing in ArangoDB + $updated = document::update($product->__document(), errors: $errors); + + // Incrementing the counter of updated categories + if ($updated && !$created) ++$products_updated; + } else throw new exception("Failed to initialize product: {$row['name']} ($number)"); + } + + // Writing to the registry of handled categories and products + $handled['products'][] = $row['identifier']; + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + } + + // Deleting old categories + foreach ( + category::_read( + /* filter: sprintf('%s - d.updated > 3600', time()), */ + sort: 'd.updated DESC', + amount: 100000, + errors: $errors + ) as $document + ) { + // Iterating over categories + + // Initializing the category + $category = new category(document: $document); + + if ( + $category instanceof category + && array_search($category->identifier, $handled['categories']) === false + ) { + // Not found identifier of the product in the buffer of handled categories and products + + // Deleting images of the category from storage + static::delete(STORAGE . DIRECTORY_SEPARATOR . 'categories' . DIRECTORY_SEPARATOR . $category->identifier, errors: $errors); + + // Deleting entries of the category in ArangoDB + entry::banish($category, errors: $errors); + + // Deleting the category in ArangoDB + document::delete($category->__document(), errors: $errors); + + // Incrementing the counter of deleted categories + ++$categories_deleted; + } + } + + // Deleting old products + foreach ( + product::_read( + /* filter: sprintf('%s - d.updated > 3600', time()), */ + sort: 'd.updated DESC', + amount: 100000, + errors: $errors + ) as $document + ) { + // Iterating over products + + // Initializing the category + $product = new product(document: $document); + + if ( + $product instanceof product + && array_search($product->identifier, $handled['products']) === false + ) { + // Not found identifier of the product in the buffer of handled categories and products + + // Deleting images of the product from storage + static::delete(STORAGE . DIRECTORY_SEPARATOR . 'products' . DIRECTORY_SEPARATOR . $product->identifier, errors: $errors); + + // Deleting entries of the product in ArangoDB + entry::banish($product, errors: $errors); + + // Deleting the product in ArangoDB + document::delete($product->__document(), errors: $errors); + + // Incrementing the counter of deleted products + ++$products_deleted; + } + } + + // Counting new documents + $categories_new = collection::count(category::COLLECTION, errors: $errors); + $products_new = collection::count(product::COLLECTION, errors: $errors); + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + } +} diff --git a/mirzaev/arming_bot/system/models/categories.php b/mirzaev/arming_bot/system/models/categories.php deleted file mode 100755 index ce3a973..0000000 --- a/mirzaev/arming_bot/system/models/categories.php +++ /dev/null @@ -1,294 +0,0 @@ - - */ -class categories extends core implements arangodb_document_interface -{ - use arangodb_document_trait; - - /** - * Name of the collection in ArangoDB - */ - public const string COLLECTION = 'THIS_COLLECTION_SHOULD_NOT_EXIST'; - - /** - * Запись категории - * - * @param string $name Название - * @param arrau $labels Ярлыки для отображения в интерфейсе ['EN' => "Label"] - * @param array $hierarchy Иерархия вложенности (от родителей к потомкам: [ model, model ... ]) - * @param array &$errors Registry of errors - * - * @return string|null Идентификатор (_id) документа, если создан - */ - public static function write( - string $name, - array $labels = ['RU' => 'Без названия'], - array $hierarchy = [], - array &$errors = [] - ): string|null { - try { - if (collection::init(core::$arangodb->session, static::COLLECTION)) { - // Инициализирована коллекция - - // Создание категории - $category = document::write( - core::$arangodb->session, - static::COLLECTION, - [ - 'name' => $name, - 'labels' => $labels, - 'version' => ROBOT_VERSION - ] - ); - - if ($category) { - // Создана категория - - /* if (collection::init(core::$arangodb->session, 'entry', true)) { - // Инициализирована коллекция - - foreach ($hierarchy as $model) { - // Перебор иерархической структуры категорий - - // Инициализация вложенной категории (следующей в массиве) - $next = current($hierarchy); - - // Поиск ребра описывающего иерархическую связь - - document::write(core::$arangodb->session, 'entry'); - } - } else throw new exception('Failed to initialize document collection: ' . static::COLLECTION); */ - } - } else throw new exception('Failed to initialize edge collection: entry'); - } catch (exception $e) { - // Write to the registry of errors - $errors[] = [ - 'text' => $e->getMessage(), - 'file' => $e->getFile(), - 'line' => $e->getLine(), - 'stack' => $e->getTrace() - ]; - } - - // Выход (провал) - return null; - } - - /** - * Поиск вхождений (подкатегории или товары) - * - * Находит вхождения через ребро entry - * Генерирует _type со значениями "category" и "product" - * относительно того есть ли у документа ещё вложения (у product вложений быть не может) - * Объединяет возвращаемые объекты документа с переменной _type - * - * @param string|null $category Category identifier (_id) - * @param string|null $filter Expression for filtering (AQL) - * @param string|null $sort Expression for sorting (AQL) - * @param int $page Страница - * @param int $amount Количество товаров на странице - * @param array &$errors Registry of errors - * - * @return array Массив с найденными вхождениями (может быть пустым) - */ - public static function entries( - ?string $category = null, - ?string $filter = null, - ?string $sort = 'v.promotion DESC, v.position ASC, v.created DESC', - int $page = 1, - int $amount = 100, - array &$errors = [] - ): array { - try { - if (collection::init(core::$arangodb->session, static::COLLECTION)) { - // Инициализирована коллекция - - if ($documents = collection::search( - core::$arangodb->session, - sprintf( - << $e->getMessage(), - 'file' => $e->getFile(), - 'line' => $e->getLine(), - 'stack' => $e->getTrace() - ]; - } - - // Выход (провал) - return []; - } - - /** - * Поиск категорий и товаров - * - * Ищет категории и товары по коллекции рёбер entry из _from и _to - * - * @param array &$errors Registry of errors - * - * @return array Массив с уникализированными найденными коллекциями (может быть пустым) - */ - protected static function collections( - array &$errors = [] - ): array { - try { - if (collection::init(core::$arangodb->session, $collection = 'entry')) { - // Инициализирована коллекция - - if ($names = collection::search( - core::$arangodb->session, - sprintf( - <<getAll(); - } else return []; - } else throw new exception('Failed to initialize edge collection: entry'); - } catch (exception $e) { - // Write to the registry of errors - $errors[] = [ - 'text' => $e->getMessage(), - 'file' => $e->getFile(), - 'line' => $e->getLine(), - 'stack' => $e->getTrace() - ]; - } - - // Выход (провал) - return []; - } - - /** - * Поиск категории по названияю - * - * Перебирает все коллекции из self::collections() и ищет в них соответствие с параметром name - * ВНИМАНИЕ: НЕЛЬЗЯ ДОПУСКАТЬ ОДИНАКОВЫЕ НАЗВАНИЯ СРЕДИ РАЗНЫХ КАТЕГОРИЙ - * - * @param string $name Name of a collection - * @param array &$errors Registry of errors - * - * @return categories|null Модель имплементирующая документ с категорией, если была найдена - */ - public static function category( - string $name, - array &$errors = [] - ): ?categories { - try { - // Инициалиация списка коллекций - $collections = self::collections($errors); - - if (count($collections) > 0) { - // Найдены коллекции - - // Инициализация буфера части запроса со списком коллекций для аргументов UNION() - $union = []; - foreach ($collections as $collection) $union[] = "FOR d IN $collection RETURN d"; - unset($collection); - - if ($document = collection::search( - core::$arangodb->session, - sprintf( - <<getId())[0]; - - if (class_exists($model = "mirzaev\\arming_bot\\models\\$collection")) { - // Найдена модель имплементирующая документы из этой коллекции - - // Инициализация объекта модели - $object = new $model; - - if ($object instanceof categories) { - // Объект является инстанцией категории (то есть не товара) - - // Запись инстанции документа в объект модели - $object->document = $document; - - // Возврат (успех) - return $object; - } - } - } else return null; - } - } catch (exception $e) { - // Write to the registry of errors - $errors[] = [ - 'text' => $e->getMessage(), - 'file' => $e->getFile(), - 'line' => $e->getLine(), - 'stack' => $e->getTrace() - ]; - } - - // Выход (провал) - return null; - } -} diff --git a/mirzaev/arming_bot/system/models/category.php b/mirzaev/arming_bot/system/models/category.php index cc7b4b0..baf0515 100755 --- a/mirzaev/arming_bot/system/models/category.php +++ b/mirzaev/arming_bot/system/models/category.php @@ -5,18 +5,81 @@ declare(strict_types=1); namespace mirzaev\arming_bot\models; // Files of the project -use mirzaev\arming_bot\models\categories; +use mirzaev\arming_bot\models\core, + mirzaev\arming_bot\models\traits\document as arangodb_document_trait, + mirzaev\arming_bot\models\interfaces\document as arangodb_document_interface; + +// Framework for ArangoDB +use mirzaev\arangodb\collection, + mirzaev\arangodb\document; + +// Built-in libraries +use exception; + /** - * Model of a category + * Model of category * * @package mirzaev\arming_bot\models + * + * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License * @author Arsen Mirzaev Tatyano-Muradovich */ -final class category extends categories +final class category extends core implements arangodb_document_interface { + use arangodb_document_trait; + /** * Name of the collection in ArangoDB */ final public const string COLLECTION = 'category'; + + /** + * Write the category + * + * @param int $identifier Identifier (unique) + * @param array $name Name [['en' => value], ['ru' => значение]] + * @param int|null $position Position for soring in the catalog (ASC) + * @param array &$errors Registry of errors + * + * @return string|null Identifier (_id) of the document in ArangoDB, if created + * + * @todo + * 1. Bind parameters + */ + public static function write( + int $identifier, + array $name = [['en' => 'ERROR']], + ?int $position = null, + array &$errors = [] + ): string|null { + try { + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + // Initialized the collection + + // Writing to ArangoDB and exit (success) + return document::write( + static::COLLECTION, + [ + 'identifier' => $identifier, + 'name' => $name, + 'position' => $position, + 'version' => ROBOT_VERSION + ], + errors: $errors + ); + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } } diff --git a/mirzaev/arming_bot/system/models/connect.php b/mirzaev/arming_bot/system/models/connect.php new file mode 100755 index 0000000..366a9b4 --- /dev/null +++ b/mirzaev/arming_bot/system/models/connect.php @@ -0,0 +1,36 @@ + + */ +final class connect extends core implements arangodb_document_interface +{ + use arangodb_document_trait; + + /** + * Name of the collection in ArangoDB + */ + final public const string COLLECTION = 'connect'; + + /** + * Type of the collection in ArangoDB + */ + public const type TYPE = type::edge; +} diff --git a/mirzaev/arming_bot/system/models/core.php b/mirzaev/arming_bot/system/models/core.php index a6762ac..6b82622 100755 --- a/mirzaev/arming_bot/system/models/core.php +++ b/mirzaev/arming_bot/system/models/core.php @@ -10,11 +10,10 @@ use mirzaev\minimal\model; // Framework for ArangoDB use mirzaev\arangodb\connection as arangodb, mirzaev\arangodb\collection, - mirzaev\arangodb\document; + mirzaev\arangodb\enumerations\collection\type; // Libraries for ArangoDB -use ArangoDBClient\Document as _document, - ArangoDBClient\DocumentHandler as _document_handler; +use ArangoDBClient\Document as _document; // Built-in libraries use exception; @@ -23,6 +22,8 @@ use exception; * Core of models * * @package mirzaev\arming_bot\models + * + * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License * @author Arsen Mirzaev Tatyano-Muradovich */ class core extends model @@ -47,6 +48,11 @@ class core extends model */ public const string COLLECTION = 'THIS_COLLECTION_SHOULD_NOT_EXIST'; + /** + * Type of the collection in ArangoDB + */ + public const type TYPE = type::document; + /** * Constructor of an instance * @@ -66,7 +72,7 @@ class core extends model if (isset($arangodb)) { // Recieved an instance of a session of ArangoDB - // Write 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); } else { // Not recieved an instance of a session of ArangoDB @@ -78,14 +84,15 @@ class core extends model } /** - * Read from ArangoDB + * Read document from ArangoDB * * @param string $filter Expression for filtering (AQL) * @param string $sort Expression for sorting (AQL) * @param int $amount Amount of documents for collect * @param int $page Page * @param string $return Expression describing the parameters to return (AQL) - * @param array &$errors The registry on errors + * @param array $parameters Binded parameters for placeholders ['placeholder' => parameter] + * @param array &$errors Registry of errors * * @return mixed An array of instances of documents from ArangoDB, if they are found */ @@ -95,37 +102,40 @@ class core extends model int $amount = 1, int $page = 1, string $return = 'd', + array $parameters = [], array &$errors = [] ): _document|array|null { try { - if (collection::init(static::$arangodb->session, static::COLLECTION)) { + if (collection::initialize(static::COLLECTION, static::TYPE)) { // Initialized the collection - // Read from ArangoDB and exit (success) - $result = collection::search( - static::$arangodb->session, + // Read from ArangoDB + $result = collection::execute( sprintf( <<<'AQL' - FOR d IN %s + FOR d IN @@collection %s %s - LIMIT %d, %d - RETURN %s + LIMIT @offset, @amount + RETURN @return AQL, - static::COLLECTION, empty($filter) ? '' : "FILTER $filter", empty($sort) ? '' : "SORT $sort", - --$page <= 0 ? 0 : $page * $amount, - $amount, - $return - ) + ), + [ + '@collection' => static::COLLECTION, + 'offset' => --$page <= 0 ? 0 : $page * $amount, + 'amount' => $amount, + 'return' => $return + ] + $parameters, + errors: $errors ); - // Выход (успех) + // Exit (success) return is_array($result) ? $result : [$result]; - } else throw new exception('Failed to initialize the collection'); + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); } catch (exception $e) { - // Write to the registry of errors + // Writing to registry of errors $errors[] = [ 'text' => $e->getMessage(), 'file' => $e->getFile(), @@ -138,50 +148,6 @@ class core extends model return null; } - /** - * Update in ArangoDB - * - * @param _document $instance Instance of the document from ArangoDB - * - * @return bool Writed to ArangoDB without errors? - */ - public static function _update(_document $instance): bool - { - // Update in ArangoDB and exit (success) - return document::update(static::$arangodb->session, $instance); - } - - /** - * Delete from ArangoDB - * - * @param _document $instance Instance of the document from ArangoDB - * @param array &$errors The registry on errors - * - * @return bool Deleted from ArangoDB without errors? - */ - public static function _delete(_document $instance, array &$errors = []): bool - { - try { - if (collection::init(static::$arangodb->session, static::COLLECTION)) { - // Initialized the collection - - // Delete from ArangoDB and exit (success) - return (new _document_handler(static::$arangodb->session))->remove($instance); - } else throw new exception('Failed to initialize the collection'); - } catch (exception $e) { - // Write to the registry of errors - $errors[] = [ - 'text' => $e->getMessage(), - 'file' => $e->getFile(), - 'line' => $e->getLine(), - 'stack' => $e->getTrace() - ]; - } - - // Exit (fail) - return false; - } - /** * Write * @@ -205,7 +171,7 @@ class core extends model if ($value instanceof arangodb) { // Recieved an appropriate value - // Write the property and exit (success) + // Writing the property and exit (success) self::$arangodb = $value; } else { // Recieved an inappropriate value diff --git a/mirzaev/arming_bot/system/models/entry.php b/mirzaev/arming_bot/system/models/entry.php new file mode 100755 index 0000000..0080487 --- /dev/null +++ b/mirzaev/arming_bot/system/models/entry.php @@ -0,0 +1,318 @@ + + */ +final class entry extends core implements arangodb_document_interface +{ + use arangodb_document_trait; + + /** + * Name of the collection in ArangoDB + */ + final public const string COLLECTION = 'entry'; + + /** + * Type of the collection in ArangoDB + */ + public const type TYPE = type::edge; + + /** + * Write an entry + * + * @param category|product $from Descendant document + * @param category $to Ascendant document + * @param array &$errors Registry of errors + * + * @return string|null Identifier (_id) of instance of the entry document in ArangoDB, if created + */ + public static function write( + category|product $from, + category $to, + array &$errors = [] + ): string|null { + try { + if (collection::initialize($from::COLLECTION, $from::TYPE, errors: $errors)) { + if (collection::initialize($to::COLLECTION, $to::TYPE, errors: $errors)) { + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + // Initialized collections + + // Creating the entry and exit (success) + return document::write( + static::COLLECTION, + [ + '_from' => $from->getId(), + '_to' => $to->getId(), + 'version' => ROBOT_VERSION ?? '0.0.0' + ], + errors: $errors + ); + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); + } else throw new exception('Failed to initialize ' . $to::TYPE . ' collection: ' . $to::COLLECTION); + } else throw new exception('Failed to initialize ' . $from::TYPE . ' collection: ' . $from::COLLECTION); + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } + + /** + * Find ascendants + * + * Find ascendants that are not descendants for anyone + * + * @param category|product $descendant Descendant document + * @param array &$errors Registry of errors + * + * @return array|null Ascendants that are not descendants for anyone, if found + */ + public static function ascendants( + category|product $descendant, + array &$errors = [] + ): ?array { + try { + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + // Initialized the collection + if ($ascendants = collection::execute( + <<<'AQL' + FOR d IN @@collection + FOR ascendant IN OUTBOUND d @@edge + RETURN DISTINCT ascendant + AQL, + [ + '@collection' => $descendant::COLLECTION, + '@edge' => static::COLLECTION + ], + errors: $errors + )) { + // Found ascendants + + // Exit (success) + return is_array($ascendants) ? $ascendants : [$ascendants]; + } else return []; + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } + + + /** + * Check existence of entry between documents + * + * @param category|product $from Descendant document + * @param category $to Ascendant document + * @param array &$errors Registry of errors + * + * @return ?_document The entry edge, if found + */ + public static function check( + category|product $from, + category $to, + array &$errors = [] + ): ?_document { + try { + if (collection::initialize($from::COLLECTION, $from::TYPE, errors: $errors)) { + if (collection::initialize($to::COLLECTION, $to::TYPE, errors: $errors)) { + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + // Initialized collections + + if ($entry = collection::execute( + <<<'AQL' + FOR d IN @@collection + FILTER d._from == @from && d._to == @to + SORT d.updated DESC, d.created DESC + LIMIT 1 + RETURN d + AQL, + [ + '@collection' => static::COLLECTION, + 'from' => $from->getId(), + 'to' => $to->getId() + ], + errors: $errors + )) { + // Found the entry between $from and $to + + // Exit (success) + return is_array($entry) ? $entry[0] : $entry; + } else return null; + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); + } else throw new exception('Failed to initialize ' . $to::TYPE . ' collection: ' . $to::COLLECTION); + } else throw new exception('Failed to initialize ' . $from::TYPE . ' collection: ' . $from::COLLECTION); + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } + + /** + * Поиск вхождений (подкатегории или товары) + * + * Находит вхождения через ребро entry + * Генерирует _type со значениями "category" и "product" + * относительно того есть ли у документа ещё вложения + * (подразумевается, что у product вложений быть не может) + * Объединяет возвращаемые объекты документа с переменной _type + * + * @param category|product $document Ascendant document + * @param string|null $filter Expression for filtering (AQL) + * @param string|null $sort Expression for sorting (AQL) + * @param int $page Страница + * @param int $amount Количество товаров на странице + * @param array &$errors Registry of errors + * + * @return array Массив с найденными вхождениями (может быть пустым) + */ + public static function search( + category|product $document, + ?string $filter = 'v.deleted != true && v.hidden != true', + ?string $sort = 'v.position ASC, v.created DESC', + int $page = 1, + int $amount = 100, + array &$errors = [] + ): array { + try { + if (collection::initialize($document::COLLECTION, $document::TYPE, errors: $errors)) { + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + // Initialized collections + + if ($documents = collection::execute( + sprintf( + <<<'AQL' + FOR v IN 1..1 INBOUND @document GRAPH @graph + %s + %s + LIMIT @offset, @amount + LET _type = (FOR v2 IN INBOUND v._id GRAPH @graph RETURN v2)[0] ? "category" : "product" + RETURN MERGE(v, {_type}) + AQL, + empty($filter) ? '' : "FILTER $filter", + empty($sort) ? '' : "SORT $sort", + ), + [ + 'grapth' => 'catalog', + 'document' => $document->getId(), + 'offset' => --$page <= 0 ? $page = 0 : $page * $amount, + 'amount' => $amount + ], + errors: $errors + )) { + // 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 ' . $document::TYPE . ' collection: ' . $document::COLLECTION); + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return []; + } + + /** + * Banish the document from the catalog + * + * Removes all entry edges associated with the document + * + * @param categoru|product $document Document for banishing + * @param array &$errors Registry of errors + * + * @return void + */ + public static function banish(category|product $document, array &$errors = []): void + { + try { + if (collection::initialize($document::COLLECTION, $document::TYPE, errors: $errors)) { + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + // Initialized collections + + // Execute and exit (success) + collection::execute( + <<<'AQL' + FOR d IN @@collection + // FILTER d._from == @_id || d._to == @_id + FILTER d._from == @_id + REMOVE d IN @@collection + AQL, + [ + '@collection' => static::COLLECTION, + '_id' => $document->getId() + ], + errors: $errors + ); + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); + } else throw new exception('Failed to initialize ' . $document::TYPE . ' collection: ' . $document::COLLECTION); + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return; + } +} diff --git a/mirzaev/arming_bot/system/models/part.php b/mirzaev/arming_bot/system/models/part.php deleted file mode 100755 index a9284f0..0000000 --- a/mirzaev/arming_bot/system/models/part.php +++ /dev/null @@ -1,22 +0,0 @@ - - */ -final class part extends categories -{ - /** - * Name of the collection in ArangoDB - */ - final public const string COLLECTION = 'part'; -} diff --git a/mirzaev/arming_bot/system/models/product.php b/mirzaev/arming_bot/system/models/product.php index bb9a0c8..5388d54 100755 --- a/mirzaev/arming_bot/system/models/product.php +++ b/mirzaev/arming_bot/system/models/product.php @@ -5,15 +5,13 @@ declare(strict_types=1); namespace mirzaev\arming_bot\models; // 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; // Framework for ArangoDB use mirzaev\arangodb\collection, mirzaev\arangodb\document; -// Library для ArangoDB -use ArangoDBClient\Document as _document; - // Built-in libraries use exception; @@ -21,155 +19,60 @@ use exception; * Model of a product * * @package mirzaev\arming_bot\models + * + * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License * @author Arsen Mirzaev Tatyano-Muradovich */ final class product extends core { + use arangodb_document_trait; + /** * Name of the collection in ArangoDB */ final public const string COLLECTION = 'product'; /** - * Чтение товаров + * Write a product * - * @param string|null $search Поиск - * @param string|null $filter Фильтр - * @param string|null $sort Сортировка - * @param int $page Страница - * @param int $amount Количество товаров на странице - * @param string|null $return + * @param int $identifier Identifier (unique) + * @param array $name Name [['en' => value], ['ru' => значение]] + * @param array|null $description Description [['en' => value], ['ru' => значение]] + * @param float $cost Cost + * @param float $weight Weight + * @param array $dimensions Dimensions ['x' => 0.0, 'y' => 0.0, 'z' => 0.0] + * @param array $images Images (first will be thumbnail) + * @param int|null $position Position for sorting in the catalog (ASC) + * @param array $data Data * @param array &$errors Registry of errors * - * @return array Массив с найденными товарами (может быть пустым) - */ - public static function read( - ?string $search = null, - ?string $filter = 'd.deleted != true && d.hidden != true', - ?string $sort = 'd.promotion DESC, d.position ASC, d.created DESC', - int $page = 1, - int $amount = 100, - ?string $return = 'd', - array &$errors = [] - ): array { - try { - if (collection::init(core::$arangodb->session, self::COLLECTION)) { - // Инициализирована коллекция - - // Инициализация строки запроса - $aql = sprintf( - <<session, - sprintf( - $aql . << $e->getMessage(), - 'file' => $e->getFile(), - 'line' => $e->getLine(), - 'stack' => $e->getTrace() - ]; - } - - // Выход (провал) - return []; - } - - /** - * Запись товара + * @return string|null Identifier (_id) of instance of the product document in ArangoDB, if created * - * @param string $title Заголовок - * @param string|null $description Описание - * @param float $cost Цена - * @param float $weight Вес - * @param array $dimensions Габариты (float) - * @param array $images Изображения (первое - обложка) (https, путь в storage, иначе будет поиск в storage) - * @param array $hierarchy Иерархия вложенности (от родителей к потомкам: [ model, model ... ]) - * @param array $data Дополнительные данные - * @param array &$errors Registry of errors - * - * @return string|null Идентификатор (_id) документа (товара), если создан + * @todo + * 1. Bind parameters */ public static function write( - string $title, - ?string $description = null, + int $identifier, + array $name = [['en' => 'ERROR']], + ?array $description = [['en' => 'ERROR']], float $cost = 0, float $weight = 0, array $dimensions = ['x' => 0, 'y' => 0, 'z' => 0], array $images = [], - array $hierarchy = [], + ?int $position = null, array $data = [], array &$errors = [] ): string|null { try { - if (collection::init(core::$arangodb->session, self::COLLECTION)) { - // Инициализирована коллекция + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + // Initialized the collection - // Создание товара - $product = document::write( - core::$arangodb->session, - self::COLLECTION, + // Writing in ArangoDB and exit (success) + return document::write( + static::COLLECTION, [ - 'title' => $title, + 'identifier' => $identifier, + 'name' => $name, 'description' => $description, 'cost' => $cost ?? 0, 'weight' => $weight ?? 0, @@ -179,31 +82,14 @@ final class product extends core 'z' => $dimensions['z'] ?? 0, ], 'images' => $images, + 'position' => $position, 'version' => ROBOT_VERSION - ] + $data + ] + $data, + errors: $errors ); - - if ($product) { - // Создан товар - - if (collection::init(core::$arangodb->session, 'entry', true)) { - // Инициализирована коллекция - - foreach ($hierarchy as $model) { - // Перебор иерархической структуры категорий - - // Инициализация вложенной категории (следующей в массиве) - $next = current($hierarchy); - - // Поиск ребра описывающего иерархическую связь - - document::write(core::$arangodb->session, 'entry'); - } - } else throw new exception('Failed to initialize document collection: ' . self::COLLECTION); - } - } else throw new exception('Failed to initialize document collection: entry'); + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); } catch (exception $e) { - // Write to the registry of errors + // Writing to the registry of errors $errors[] = [ 'text' => $e->getMessage(), 'file' => $e->getFile(), @@ -212,7 +98,163 @@ final class product extends core ]; } - // Выход (провал) + // Exit (fail) return null; } + + /** + * Read products + * + * @param string|null $search Search (text) + * @param string|null $filter Flter (AQL) + * @param string|null $sort Sort (AQL) + * @param int $page Page + * @param int $amount Amount per page + * @param string|null $return Return (AQL) + * @param string $language Language code (en, ru...) + * @param array &$errors Registry of errors + * + * @return array Массив с найденными товарами (может быть пустым) + */ + public static function read( + ?string $search = null, + ?string $filter = 'd.deleted != true && d.hidden != true', + ?string $sort = 'd.position ASC, d.created DESC', + int $page = 1, + int $amount = 100, + ?string $return = 'd', + string $language = 'en', + array &$errors = [] + ): array { + try { + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + // Initialized the collection + + // Initializing the query + $aql = <<<'AQL' + FOR d IN @@collection + AQL; + + if ($search) { + // Requested search + + // Writing to the query + $aql .= <<<'AQL' + SEARCH + LEVENSHTEIN_MATCH( + d.name.@language, + TOKENS(@search, @analyzer)[0], + 1, + false + ) OR + levenshtein_match( + d.description.@language, + tokens(@search, @analyzer)[0], + 1, + false + ) OR + levenshtein_match( + d.compatibility.@language, + tokens(@search, @analyzer)[0], + 1, + 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, + empty($filter) ? '' : "FILTER $filter", + empty($sort) ? '' : "SORT $sort", + empty($return) ? 'd' : $return + ), + [ + '@collection' => $search ? static::COLLECTION . 's_search' : static::COLLECTION, + 'search' => $search, + 'language' => $language, + 'analyzer' => "text_$language", + 'offset' => --$page <= 0 ? $page = 0 : $page * $amount, + 'amount' => $amount, + ], + errors: $errors + ); + + if ($documents) { + // Found products + + // Exit (success) + return is_array($documents) ? $documents : [$documents]; + } else return []; + } else throw new exception('Failed to initialize ' . static::COLLECTION . ' collection: ' . static::COLLECTION); + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return []; + } + + /** + * Collect parameter from all products + * + * @param string $name Name of the parameter (AQL path) + * @param array &$errors Registry of errors + * + * @return array Array with found unique parameter values from all products (can be empty) + */ + public static function collect( + string $name = 'd._key', + array &$errors = [] + ): array { + try { + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + // Initialized the collection + + if ($values = collection::execute( + <<<'AQL' + FOR d IN @@collecton + RETURN DISTINCT @parameter + AQL, + [ + '@collection' => static::COLLECTION, + 'parameter' => $name + ], + errors: $errors + )) { + // Found parameters + + // Exit (success) + return $values; + } else return []; + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return []; + } } diff --git a/mirzaev/arming_bot/system/models/session.php b/mirzaev/arming_bot/system/models/session.php index 059e455..2ef0934 100755 --- a/mirzaev/arming_bot/system/models/session.php +++ b/mirzaev/arming_bot/system/models/session.php @@ -6,6 +6,7 @@ namespace mirzaev\arming_bot\models; // Files of the project use mirzaev\arming_bot\models\account, + mirzaev\arming_bot\models\connect, mirzaev\arming_bot\models\enumerations\session as verification, mirzaev\arming_bot\models\traits\status, mirzaev\arming_bot\models\traits\document as arangodb_document_trait, @@ -25,6 +26,8 @@ use exception; * Model of a session * * @package mirzaev\arming_bot\models + * + * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License * @author Arsen Mirzaev Tatyano-Muradovich */ final class session extends core implements arangodb_document_interface @@ -37,12 +40,12 @@ final class session extends core implements arangodb_document_interface final public const string COLLECTION = 'session'; /** - * Type of session verification( + * Type of sessions verification */ public const verification VERIFICATION = verification::hash_else_address; /** - * Constructor of an instance + * Constructor of instance * * Initialize of a session and write them to the $this->document property * @@ -50,63 +53,69 @@ final class session extends core implements arangodb_document_interface * @param ?int $expires Date of expiring of the session (used for creating a new session) * @param array &$errors Registry of errors * - * @return static instance of the ArangoDB document of session + * @return static */ public function __construct(?string $hash = null, ?int $expires = null, array &$errors = []) { try { - if (collection::init(static::$arangodb->session, self::COLLECTION)) { + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { // Initialized the collection - if (isset($hash) && $document = $this->hash($hash, $errors)) { - // Found an instance of the ArangoDB document of session and received a session hash + if (isset($hash) && $document = $this->hash($hash, errors: $errors)) { + // Found the instance of the ArangoDB document of session and received a session hash // Writing document instance of the session from ArangoDB to the property of the implementing object - $this->document = $document; - } else if (static::VERIFICATION === verification::hash_else_address && $document = $this->address($_SERVER['REMOTE_ADDR'], $errors)) { - // Found an instance of the ArangoDB document of session and received a session hash + $this->__document($document); + } else if (static::VERIFICATION === verification::hash_else_address && $document = $this->address($_SERVER['REMOTE_ADDR'], errors: $errors)) { + // Found the instance of the ArangoDB document of session and received a session hash // Writing document instance of the session from ArangoDB to the property of the implementing object - $this->document = $document; + $this->__document($document); } else { - // Not found an instance of the ArangoDB document of session + // Not found the instance of the ArangoDB document of session // Initializing a new session and write they into ArangoDB - $_id = document::write($this::$arangodb->session, self::COLLECTION, [ - 'active' => true, - 'expires' => $expires ?? time() + 604800, - 'address' => $_SERVER['REMOTE_ADDR'], - 'x-forwarded-for' => $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null, - 'referer' => $_SERVER['HTTP_REFERER'] ?? null, - 'useragent' => $_SERVER['HTTP_USER_AGENT'] ?? null - ]); + $_id = document::write( + static::COLLECTION, + [ + 'active' => true, + 'expires' => $expires ?? time() + 604800, + 'address' => $_SERVER['REMOTE_ADDR'], + 'x-forwarded-for' => $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null, + 'referer' => $_SERVER['HTTP_REFERER'] ?? null, + 'useragent' => $_SERVER['HTTP_USER_AGENT'] ?? null + ] + ); - if ($session = collection::search($this::$arangodb->session, sprintf( - << %d && d.active == true + if ($session = collection::execute( + <<<'AQL' + FOR d IN @@collection + FILTER d._id == @_id && d.expires > @time && d.active == true RETURN d AQL, - self::COLLECTION, - $_id, - time() - ))) { - // Found an instance of just created new session + [ + '@collection' => static::COLLECTION, + '_id' => $_id, + 'time' => time() + ], + errors: $errors + )) { + // Found the instance of just created new session - // Generate a hash and write into an instance of the ArangoDB document of session property + // Generating a hash and write into the instance of the ArangoDB document of session property $session->hash = sodium_bin2hex(sodium_crypto_generichash($_id)); - if (document::update($this::$arangodb->session, $session)) { - // Is writed update + if (document::update($session, errors: $errors)) { + // Update is writed to ArangoDB - // Writing document instance of the session from ArangoDB to the property of the implementing object - $this->document = $session; - } else throw new exception('Could not write the session data'); - } else throw new exception('Could not create or find just created session'); + // Writing instance of the session document from ArangoDB to the property of the implementing object + $this->__document($session); + } else throw new exception('Failed to write the session data'); + } else throw new exception('Failed to create or find just created session'); } - } else throw new exception('Could not initialize the collection'); + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); } catch (exception $e) { - // Write to the registry of errors + // Writing to the registry of errors $errors[] = [ 'text' => $e->getMessage(), 'file' => $e->getFile(), @@ -121,44 +130,47 @@ final class session extends core implements arangodb_document_interface * * @param array &$errors Registry of errors * - * @return account|null An object implementing the account instance from the database, if found + * @return account|null An object implements the instance of the account document from ArangoDB, if found */ public function account(array &$errors = []): ?account { try { - if (collection::init(core::$arangodb->session, static::COLLECTION)) { - if (collection::init(core::$arangodb->session, 'connect', true)) { - if (collection::init(core::$arangodb->session, account::COLLECTION)) { - // Инициализирована коллекция + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + if (collection::initialize(connect::COLLECTION, connect::TYPE, errors: $errors)) { + if (collection::initialize(account::COLLECTION, account::TYPE, errors: $errors)) { + // Initialized collections - if ($document = collection::search( - core::$arangodb->session, - sprintf( - <<getId(), - ) - )) { - // Найден аккаунт + // Search for connected account + $document = collection::execute( + << $this->getId() + ], + errors: $errors + ); - // Инициализация объекта аккаунта + if ($document instanceof _document) { + // Found connected account + + // Initializing the implement object of the instance of sesson document from ArangoDB $account = new account; - // Запись инстанции документа в объект + // Writing the instance of session document from ArangoDB to the implement object $account->__document($document); // Exit (success) return $account; } else return null; - } else throw new exception('Failed to initialize document collection: ' . account::COLLECTION); - } else throw new exception('Failed to initialize edge collection: connect'); - } else throw new exception('Failed to initialize document collection: ' . static::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 ' . static::TYPE . ' collection: ' . static::COLLECTION); } catch (exception $e) { - // Write to the registry of errors + // Writing to the registry of errors $errors[] = [ 'text' => $e->getMessage(), 'file' => $e->getFile(), @@ -182,25 +194,28 @@ final class session extends core implements arangodb_document_interface public function connect(account $account, array &$errors = []): ?string { try { - if (collection::init(core::$arangodb->session, static::COLLECTION)) { - if (collection::init(core::$arangodb->session, 'connect', true)) { - if (collection::init(core::$arangodb->session, account::COLLECTION)) { + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + if (collection::initialize(connect::COLLECTION, connect::TYPE, errors: $errors)) { + if (collection::initialize(account::COLLECTION, account::TYPE, errors: $errors)) { // Collections initialized + // The instance of the session document from ArangoDB is initialized? + isset($this->document) || throw new exception('The instance of the sessoin document from ArangoDB is not initialized'); + // Writing document and exit (success) return document::write( - core::$arangodb->session, - 'connect', + connect::COLLECTION, [ '_from' => $account->getId(), '_to' => $this->document->getId() - ] + ], + errors: $errors ); - } else throw new exception('Failed to initialize document collection: ' . account::COLLECTION); - } else throw new exception('Failed to initialize edge collection: connect'); - } else throw new exception('Failed to initialize document collection: ' . static::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 ' . static::TYPE . ' collection: ' . static::COLLECTION); } catch (exception $e) { - // Write to the registry of errors + // Writing to the registry of errors $errors[] = [ 'text' => $e->getMessage(), 'file' => $e->getFile(), @@ -226,23 +241,26 @@ final class session extends core implements arangodb_document_interface public static function hash(string $hash, array &$errors = []): ?_document { try { - if (collection::init(core::$arangodb->session, static::COLLECTION)) { + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { // Collection initialized // Search the session data in ArangoDB - return collection::search(static::$arangodb->session, sprintf( - << %d && d.active == true + return collection::execute( + <<<'AQL' + FOR d IN @@collection + FILTER d.hash == @hash && d.expires > $time && d.active == true RETURN d AQL, - static::COLLECTION, - $hash, - time() - )); - } else throw new exception('Failed to initialize document collection: ' . static::COLLECTION); + [ + '@collection' => static::COLLECTION, + 'hash' => $hash, + 'time' => time() + ], + errors: $errors + ); + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); } catch (exception $e) { - // Write to the registry of errors + // Writing to the registry of errors $errors[] = [ 'text' => $e->getMessage(), 'file' => $e->getFile(), @@ -268,23 +286,26 @@ final class session extends core implements arangodb_document_interface public static function address(string $address, array &$errors = []): ?_document { try { - if (collection::init(core::$arangodb->session, static::COLLECTION)) { + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { // Collection initialized // Search the session data in ArangoDB - return collection::search(static::$arangodb->session, sprintf( - << %d && d.active == true + return collection::execute( + <<<'AQL' + FOR d IN @@collection + FILTER d.address == @address && d.expires > @time && d.active == true RETURN d AQL, - static::COLLECTION, - $address, - time() - )); - } else throw new exception('Failed to initialize document collection: ' . static::COLLECTION); + [ + '@collection' => static::COLLECTION, + 'address' => $address, + 'time' => time() + ], + errors: $errors + ); + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); } catch (exception $e) { - // Write to the registry of errors + // Writing to the registry of errors $errors[] = [ 'text' => $e->getMessage(), 'file' => $e->getFile(), @@ -298,33 +319,33 @@ final class session extends core implements arangodb_document_interface } /** - * Write to buffer of the session + * Write to buffer of the session * * @param array $data Data for merging * @param array &$errors Registry of errors * - * @return bool Is data has written into the session buffer? + * @return bool Is data has written into the session document from ArangoDB? */ public function write(array $data, array &$errors = []): bool { try { - if (collection::init($this::$arangodb->session, self::COLLECTION)) { + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { // Initialized the collection - // An instance of the ArangoDB document of session is initialized? - if (!isset($this->document)) throw new exception('An instance of the ArangoDB document of session is not initialized'); + // The instance of the session document from ArangoDB is initialized? + isset($this->document) || throw new exception('The instance of the sessoin document from ArangoDB is not initialized'); - // Write data into buffwer of an instance of the ArangoDB document of session + // Writing data into buffer of the instance of the session document from ArangoDB $this->document->buffer = array_replace_recursive( $this->document->buffer ?? [], [$_SERVER['INTERFACE'] => array_replace_recursive($this->document->buffer[$_SERVER['INTERFACE']] ?? [], $data)] ); - // Write to ArangoDB and exit (success) - return document::update($this::$arangodb->session, $this->document) ? true : throw new exception('Не удалось записать данные в буфер сессии'); - } else throw new exception('Could not initialize the collection'); + // Writing to ArangoDB and exit (success) + return document::update($this->document, errors: $errors); + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); } catch (exception $e) { - // Write to the registry of errors + // Writing to the registry of errors $errors[] = [ 'text' => $e->getMessage(), 'file' => $e->getFile(), @@ -333,6 +354,7 @@ final class session extends core implements arangodb_document_interface ]; } + // Exit (fail) return false; } } diff --git a/mirzaev/arming_bot/system/models/settings.php b/mirzaev/arming_bot/system/models/settings.php index 4cb1a47..bdfd74e 100755 --- a/mirzaev/arming_bot/system/models/settings.php +++ b/mirzaev/arming_bot/system/models/settings.php @@ -23,6 +23,8 @@ use exception; * Model of settings * * @package mirzaev\arming_bot\models + * + * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License * @author Arsen Mirzaev Tatyano-Muradovich */ final class settings extends core implements arangodb_document_interface @@ -40,35 +42,52 @@ final class settings extends core implements arangodb_document_interface * @param array|null $create Данные для создания, если настройки не найдены * @param array &$errors Registry of errors * - * @return static|null Object implements an instance of settngs document from ArangoDB + * @return static|null Object implements the instance of settngs document from ArangoDB */ public static function active(array|null $create = null, array &$errors = []): static|null { try { - if (collection::init(core::$arangodb->session, self::COLLECTION)) { - if ($document = collection::search(core::$arangodb->session, sprintf("FOR d IN %s FILTER d.status == 'active' SORT d.updated DESC LIMIT 1 RETURN d", self::COLLECTION))) { - // Найдены активные настройки + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + // Initialized the collection - // Инициализация объекта настроек + // Search for active settings + $document = collection::execute( + <<<'AQL' + FOR d IN @@collection + FILTER d.status == 'active' + SORT d.updated DESC + LIMIT 1 + RETURN d + AQL, + [ + '@collection' => static::COLLECTION + ], + errors: $errors + ); + + if ($document instanceof _document) { + // Found active settings + + // Initializing the implement object of the instance of settings document from ArangoDB $settings = new static; - // Запись инстанции документа в объект - $settings->document = $document; + // Writing the instance of settings document from ArangoDB to the implement object + $settings->__document($document); - // Возврат (успех) + // Exit (success) return $settings; } else if ($create) { - // Не найдены активные настройки и запрошено создание + // Not found active settings and requested their creating - // Создание настроек - document::write(core::$arangodb->session, self::COLLECTION, ['status' => 'active'] + $create); + // Creating a settings + document::write(static::COLLECTION, ['status' => 'active'] + $create, errors: $errors); - // Инициализация (без создания) + // Re-search (without creating) and exit (success || fail) return static::active(errors: $errors); - } else throw new exception('Settings not found'); - } else throw new exception('Failed to initialize document collection: ' . self::COLLECTION); + } else throw new exception('Active settings not found'); + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); } catch (exception $e) { - // Write to the registry of errors + // Writing to the registry of errors $errors[] = [ 'text' => $e->getMessage(), 'file' => $e->getFile(), @@ -77,7 +96,7 @@ final class settings extends core implements arangodb_document_interface ]; } - // Выход (провал) + // Exit (fail) return null; } } diff --git a/mirzaev/arming_bot/system/models/suspension.php b/mirzaev/arming_bot/system/models/suspension.php index 28a59e1..cb5d832 100755 --- a/mirzaev/arming_bot/system/models/suspension.php +++ b/mirzaev/arming_bot/system/models/suspension.php @@ -26,6 +26,8 @@ use exception, * Model of a suspension * * @package mirzaev\arming_bot\models + * + * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License * @author Arsen Mirzaev Tatyano-Muradovich */ final class suspension extends core implements arangodb_document_interface @@ -42,27 +44,45 @@ final class suspension extends core implements arangodb_document_interface * * @param array &$errors Registry of errors * - * @return static|null Object implements an instance of suspension from ArangoDB + * @return static|null Object implements the instance of suspension from ArangoDB */ public static function search(array &$errors = []): static|null { try { - if (collection::init(core::$arangodb->session, self::COLLECTION)) { - if ($document = collection::search(core::$arangodb->session, sprintf("FOR d IN %s FILTER d.end > %u SORT d.end DESC LIMIT 1 RETURN d", self::COLLECTION, time()))) { - // Найдены активные настройки + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + // Initialized the collection - // Инициализация объекта настроек + // Search for active suspension + $document = collection::execute( + <<<'AQL' + FOR d IN @@collection + FILTER d.end > @time + SORT d.end DESC + LIMIT 1 + RETURN d + AQL, + [ + '@collection' => static::COLLECTION, + 'time' => time() + ], + errors: $errors + ); + + if ($document instanceof _document) { + // Found active suspension + + // Initializing the implement object of the instance of suspension document from ArangoDB $suspension = new static; - // Запись инстанции документа в объект - $suspension->document = $document; + // Writing the instance of suspension document from ArangoDB to the implement object + $suspension->__document($document); - // Возврат (успех) + // Exit (success) return $suspension; } else return null; - } else throw new exception('Failed to initialize document collection: ' . self::COLLECTION); + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); } catch (exception $e) { - // Write to the registry of errors + // Writing to the registry of errors $errors[] = [ 'text' => $e->getMessage(), 'file' => $e->getFile(), @@ -151,7 +171,7 @@ final class suspension extends core implements arangodb_document_interface } ); } catch (exception $e) { - // Write to the registry of errors + // Writing to the registry of errors $errors[] = [ 'text' => $e->getMessage(), 'file' => $e->getFile(), diff --git a/mirzaev/arming_bot/system/models/chat.php b/mirzaev/arming_bot/system/models/telegram.php similarity index 76% rename from mirzaev/arming_bot/system/models/chat.php rename to mirzaev/arming_bot/system/models/telegram.php index 05e682a..6340fd3 100755 --- a/mirzaev/arming_bot/system/models/chat.php +++ b/mirzaev/arming_bot/system/models/telegram.php @@ -7,26 +7,26 @@ namespace mirzaev\arming_bot\models; // Files of the project use mirzaev\arming_bot\models\core, mirzaev\arming_bot\controllers\core as controller, + mirzaev\arming_bot\models\catalog, + mirzaev\arming_bot\models\suspension, mirzaev\arming_bot\models\account; -// Фреймворк Telegram +// Framework for Telegram use Zanzara\Zanzara, Zanzara\Context, Zanzara\Telegram\Type\Input\InputFile, Zanzara\Telegram\Type\File\Document as telegram_document, - Zanzara\Telegram\Type\File\File, Zanzara\Middleware\MiddlewareNode as Node; -// Library для ArangoDB -use ArangoDBClient\Document as _document; - /** - * Model of a chat + * Model of chat (telegram) * * @package mirzaev\arming_bot\models + * + * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License * @author Arsen Mirzaev Tatyano-Muradovich */ -final class chat extends core +final class telegram extends core { /** * Экранирование символов для Markdown @@ -167,7 +167,8 @@ final class chat extends core ], [ ['text' => '🪖 Сайт', 'url' => 'https://arming.ru'], - ['text' => '🛒 Wildberries', 'url' => 'https://arming.ru'] + ['text' => '🛒 Wildberries', 'url' => 'https://www.wildberries.ru/seller/137386'], + ['text' => '🛒 Ozon', 'url' => 'https://www.ozon.ru/seller/arming-1086587/products/?miniapp=seller_1086587'], ] ] ], @@ -323,7 +324,7 @@ final class chat extends core $ctx->sendDocument(new InputFile(CATALOG_EXAMPLE), ['disable_notification' => true]); // Импорт файла - $ctx->nextStep("import"); + $ctx->nextStep([static::class, 'import'], true); }); } else { // Не авторизован доступ к настройкам @@ -342,7 +343,7 @@ final class chat extends core */ public static function import(Context $ctx): void { - if ($ctx->get('account')?->access['settings']) { + if (($account = $ctx->get('account'))?->access['settings']) { // Авторизован доступ к настройкам // Инициализация документа @@ -352,35 +353,97 @@ final class chat extends core // Инициализирован документ // Инициализация файла - $ctx->getFile($document->getFileId())->then(function ($file) use ($ctx) { + $ctx->getFile($document->getFileId())->then(function ($file) use ($ctx, $document, $account) { if ($file->getFileSize() <= 50000000) { - // Не превышает 50 мегабайт (50000000 байт) размер файла + // Не превышает 50 мегабайт (50 000 000 байт) размер файла - if ($file->getFilePath()['extension'] === 'xlsx') { + if (pathinfo(parse_url($file->getFilePath())['path'], PATHINFO_EXTENSION) === 'xlsx') { // Имеет расширение xlsx файл + // Initializing the directory in the storage + if (!file_exists($storage = STORAGE . DIRECTORY_SEPARATOR . 'import' . DIRECTORY_SEPARATOR . $account->getKey() . DIRECTORY_SEPARATOR . time())) + mkdir($storage, 0775, true); + // Сохранение файла - file_put_contents(STORAGE . DIRECTORY_SEPARATOR . 'import.xlsx', file_get_contents('https://api.telegram.org/file/bot' . KEY . '/' . $file->getFilePath())); - - // Инициализация счётчика загруженных товаров - $loaded = $created = $updated = $deleted = $old = $new = 0; - - + file_put_contents( + $import = $storage . DIRECTORY_SEPARATOR . 'import.xlsx', + file_get_contents('https://api.telegram.org/file/bot' . KEY . '/' . parse_url($file->getFilePath())['path']) + ); // Отправка сообщения - $ctx->sendMessage(<<sendMessage(sprintf( + <<<'TXT' + 🔬 *Выполняется анализ:* %s \(%s байт\) + TXT, + static::unmarkdown($document->getFileName()), + static::unmarkdown((string) $file->getFileSize()) + )) + ->then(function ($message) use ($ctx, $import) { + // Инициализация счётчика загруженных товаров + $categories_loaded + = $products_loaded + = $categories_created + = $products_created + = $categories_updated + = $products_updated + = $categories_deleted + = $products_deleted + = $categories_old + = $products_old + = $categories_new + = $products_new + = 0; - *Добавлено:* $created - *Обновлено:* $updated - *Удалено:* $deleted + // Import + catalog::import( + $import, + $categories_loaded, + $categories_created, + $categories_updated, + $categories_deleted, + $categories_old, + $categories_new, + $products_loaded, + $products_created, + $products_updated, + $products_deleted, + $products_old, + $products_new, + language: 'ru' + ); - *Опубликовано в магазине:* $old \-\> *$new* - TXT) - ->then(function ($message) use ($ctx) { - // Завершение диалога - $ctx->endConversation(); + // Отправка сообщения + $ctx->sendMessage(<<then(function ($message) use ($ctx, $products_loaded, $products_created, $products_updated, $products_deleted, $products_old, $products_new) { + $ctx->sendMessage(<<then(function ($message) use ($ctx) { + // Завершение диалога + $ctx->endConversation(); + }); + }); }); } else { // Не имеет расширение xlsx файл @@ -473,7 +536,7 @@ final class chat extends core // Поиск технических работ $suspension = suspension::search(); - if ($suspension && $suspension->targets['chat-robot']) { + if ($suspension && $suspension->targets['telegram-robot']) { // Найдена активная приостановка // Инициализация аккаунта @@ -501,7 +564,8 @@ final class chat extends core $message = "⚠️ *Работа приостановлена*\n*Оставшееся время\:* " . $suspension->message($account->language ?? controller::$settings?->language); // Добавление описания причины приостановки, если найдена - if (!empty($suspension->description)) $message .= "\n\n" . $suspension->description[$account->language ?? controller::$settings?->language] ?? array_values($suspension->description)[0]; + if (!empty($suspension->description)) + $message .= "\n\n" . $suspension->description[$account->language ?? controller::$settings?->language] ?? array_values($suspension->description)[0]; // Отправка сообщения $ctx->sendMessage($message) @@ -519,84 +583,4 @@ final class chat extends core $next($ctx); } } - - /** - * Write - * - * Write a property into an instance of the ArangoDB document - * - * @param string $name Name of the property - * @param mixed $value Content of the property - * - * @return void - */ - public function __set(string $name, mixed $value = null): void - { - // Write to the property into an instance of the ArangoDB document and exit (success) - $this->document->{$name} = $value; - } - - /** - * Read - * - * Read a property from an instance of the ArangoDB docuemnt - * - * @param string $name Name of the property - * - * @return mixed Content of the property - */ - public function __get(string $name): mixed - { - // Read a property from an instance of the ArangoDB document and exit (success) - return match ($name) { - 'arangodb' => $this::$arangodb, - default => $this->document->{$name} - }; - } - - /** - * Delete - * - * Deinitialize the property in an instance of the ArangoDB document - * - * @param string $name Name of the property - * - * @return void - */ - public function __unset(string $name): void - { - // Delete the property in an instance of the ArangoDB document and exit (success) - unset($this->document->{$name}); - } - - /** - * Check of initialization - * - * Check of initialization of the property into an instance of the ArangoDB document - * - * @param string $name Name of the property - * - * @return bool The property is initialized? - */ - public function __isset(string $name): bool - { - // Check of initializatio nof the property and exit (success) - return isset($this->document->{$name}); - } - - /** - * Execute a method - * - * Execute a method from an instance of the ArangoDB document - * - * @param string $name Name of the method - * @param array $arguments Arguments for the method - * - * @return mixed Result of execution of the method - */ - public function __call(string $name, array $arguments = []): mixed - { - // Execute the method and exit (success) - if (method_exists($this->document, $name)) return $this->document->{$name}($arguments); - } } diff --git a/mirzaev/arming_bot/system/models/traits/document.php b/mirzaev/arming_bot/system/models/traits/document.php index 135c103..e4e457d 100755 --- a/mirzaev/arming_bot/system/models/traits/document.php +++ b/mirzaev/arming_bot/system/models/traits/document.php @@ -10,9 +10,17 @@ use mirzaev\arming_bot\models\core; // Library для ArangoDB use ArangoDBClient\Document as _document; +// Framework for ArangoDB +use mirzaev\arangodb\connection as arangodb; + +// Built-in libraries +use exception; + /** * Trait for implementing a document instance from ArangoDB * + * @var protected readonly _document|null $document An instance of the ArangoDB document + * * @package mirzaev\arming_bot\models\traits * @author Arsen Mirzaev Tatyano-Muradovich */ @@ -23,6 +31,28 @@ trait document */ protected readonly _document $document; + /** + * Constructor of an instance + * + * @param bool $initialize Initialize a model? + * @param ?arangodb $arangodb Instance of a session of ArangoDB + * @param _document|null|false $document An instance of the ArangoDB document + * + * @return void + */ + public function __construct( + bool $initialize = true, + ?arangodb $arangodb = null, + _document|null|false $document = false + ) { + // For the extends system + parent::__construct($initialize, $arangodb); + + // Writing to the property + if ($document instanceof _document) $this->document = $document; + else if ($document === null) throw new exception('Failed to initialize an instance of the document from ArangoDB'); + } + /** * Write or read document * @@ -30,13 +60,13 @@ trait document * * @return _document|null Instance of document from ArangoDB */ - public function __document(?_document $document): ?_document + public function __document(?_document $document = null): ?_document { - // Write a property storing a document instance to ArangoDB - if ($document) $this->document = $document; + // Writing a property storing a document instance to ArangoDB + if ($document) $this->document ??= $document; // Read a property storing a document instance to ArangoDB and exit (success) - return $this->document; + return $this->document ?? null; } /** @@ -51,7 +81,7 @@ trait document */ public function __set(string $name, mixed $value = null): void { - // Write to the property into an instance of the ArangoDB document and exit (success) + // Writing to the property into an instance of the ArangoDB document and exit (success) $this->document->{$name} = $value; } @@ -116,6 +146,6 @@ trait document public function __call(string $name, array $arguments = []): mixed { // Execute the method and exit (success) - return method_exists($this->document, $name) ?$this->document->{$name}($arguments) ?? null : null; + return method_exists($this->document, $name) ? $this->document->{$name}($arguments) ?? null : null; } } diff --git a/mirzaev/arming_bot/system/models/traits/files.php b/mirzaev/arming_bot/system/models/traits/files.php new file mode 100755 index 0000000..1f341e0 --- /dev/null +++ b/mirzaev/arming_bot/system/models/traits/files.php @@ -0,0 +1,58 @@ + + */ +trait files +{ + /** + * Delete files recursively + * + * @param string $directory Directory + * @param array &$errors Registry of errors + * + * @return void + */ + private static function delete(string $directory, array &$errors = []): void + { + try { + if (file_exists($directory)) { + // Directory exists + + // Deleting descendant files and directories (enter to the recursion) + foreach (scandir($directory) as $file) { + if ($file === '.' || $file === '..') continue; + else if (is_dir("$directory/$file")) static::delete("$directory/$file", $errors); + else unlink("$directory/$file"); + } + + // Deleting the directory + rmdir($directory); + + // Exit (success) + return; + } else throw new exception('Directory does not exist'); + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return; + } +} diff --git a/mirzaev/arming_bot/system/models/traits/status.php b/mirzaev/arming_bot/system/models/traits/status.php index d23f3cb..69f9c39 100755 --- a/mirzaev/arming_bot/system/models/traits/status.php +++ b/mirzaev/arming_bot/system/models/traits/status.php @@ -28,7 +28,7 @@ trait status // Read from ArangoDB and exit (success) return $this->document->active ?? false; } catch (exception $e) { - // Write to the registry of errors + // Writing to the registry of errors $errors[] = [ 'text' => $e->getMessage(), 'file' => $e->getFile(), diff --git a/mirzaev/arming_bot/system/models/traits/yandex/disk.php b/mirzaev/arming_bot/system/models/traits/yandex/disk.php new file mode 100755 index 0000000..b8431c9 --- /dev/null +++ b/mirzaev/arming_bot/system/models/traits/yandex/disk.php @@ -0,0 +1,70 @@ + + */ +trait disk +{ + /** + * Download file from "Yandex Disk" + * + * @param string $uri URI of the file from "Yandex Disk" + * @param string $destination Destination to write the file + * @param array &$errors Registry of errors + * + * @return bool The file is downloaded? + */ + private static function download( + string $uri, + string $destination, + array &$errors = [] + ): bool { + try { + if (!empty($uri)) { + // Not empty URI + + if (!empty($destination)) { + // Not empty destination + + // Initializing URL of the file + $url = "https://cloud-api.yandex.net/v1/disk/public/resources/download?public_key=$uri"; + + // Checking if the file is available for download + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); + curl_close($ch); + + if ($code === 200) { + // The file is available for download + + // Downloading the file and exit (success) + return file_put_contents($destination, file_get_contents(json_decode(file_get_contents($url))?->href)) > 0; + } else throw new exception("File not available for download: $uri"); + } else throw new exception("Empty destination"); + } else throw new exception("Empty URI"); + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return false; + } +} diff --git a/mirzaev/arming_bot/system/public/index.php b/mirzaev/arming_bot/system/public/index.php index 938a499..021664a 100755 --- a/mirzaev/arming_bot/system/public/index.php +++ b/mirzaev/arming_bot/system/public/index.php @@ -12,9 +12,9 @@ use mirzaev\arming_bot\controllers\core as controller, use mirzaev\minimal\core, mirzaev\minimal\router; -/* ini_set('error_reporting', E_ALL); +ini_set('error_reporting', E_ALL); ini_set('display_errors', 1); -ini_set('display_startup_errors', 1); */ +ini_set('display_startup_errors', 1); // Версия робота define('ROBOT_VERSION', '1.0.0'); diff --git a/mirzaev/arming_bot/system/public/js/authentication.js b/mirzaev/arming_bot/system/public/js/authentication.js index fdf3a03..9a7b1cf 100755 --- a/mirzaev/arming_bot/system/public/js/authentication.js +++ b/mirzaev/arming_bot/system/public/js/authentication.js @@ -15,7 +15,10 @@ import("/js/core.js").then(() => initialization(); } }, 10); - const timeout = setTimeout(() => clearInterval(dependencies), 5000); + const timeout = setTimeout(() => { + clearInterval(dependencies); + initialization(); + }, 5000); function initialization() { const timer_for_response = setTimeout(() => { diff --git a/mirzaev/arming_bot/system/public/js/cart.js b/mirzaev/arming_bot/system/public/js/cart.js index e211050..27104a4 100755 --- a/mirzaev/arming_bot/system/public/js/cart.js +++ b/mirzaev/arming_bot/system/public/js/cart.js @@ -4,14 +4,19 @@ import("/js/core.js").then(() => import("/js/damper.js").then(() => { const dependencies = setInterval(() => { - if (typeof core === "function" && - typeof core.damper === "function") { + if ( + typeof core === "function" && + typeof core.damper === "function" + ) { clearInterval(dependencies); clearTimeout(timeout); initialization(); } }, 10); - const timeout = setTimeout(() => clearInterval(dependencies), 5000); + const timeout = setTimeout(() => { + clearInterval(dependencies); + initialization(); + }, 5000); function initialization() { if (typeof core.cart === "undefined") { diff --git a/mirzaev/arming_bot/system/public/js/catalog.js b/mirzaev/arming_bot/system/public/js/catalog.js index b2d6eb4..5bf6ec1 100755 --- a/mirzaev/arming_bot/system/public/js/catalog.js +++ b/mirzaev/arming_bot/system/public/js/catalog.js @@ -6,7 +6,12 @@ import("/js/core.js").then(() => import("/js/telegram.js").then(() => { import("/js/hotline.js").then(() => { const dependencies = setInterval(() => { - console.log(typeof core, typeof core.damper, typeof core.telegram, typeof core.hotline); + console.log( + typeof core, + typeof core.damper, + typeof core.telegram, + typeof core.hotline, + ); if ( typeof core === "function" && typeof core.damper === "function" && @@ -18,7 +23,10 @@ import("/js/core.js").then(() => initialization(); } }, 10); - const timeout = setTimeout(() => clearInterval(dependencies), 5000); + const timeout = setTimeout(() => { + clearInterval(dependencies); + initialization(); + }, 5000); function initialization() { if (typeof core.catalog === "undefined") { @@ -31,6 +39,13 @@ import("/js/core.js").then(() => */ static categories = []; + /** + * Registry of filters (instead of cookies) + */ + static filters = new Map([ + ['brand', null] + ]); + /** * Select a category (interface) * @@ -453,63 +468,63 @@ import("/js/core.js").then(() => const images = document.createElement("div"); images.classList.add("images", "unselectable"); + const button = core.telegram.api.isVisible; + + const _open = (event) => { + if (event.target === from) { + if (typeof images.hotline === "object") { + if (images.hotline.moving) return; + images.hotline.stop(); + } + + images.classList.add("extend"); + + if (button) core.telegram.api.MainButton.hide(); + + setTimeout(() => { + images.addEventListener("click", _close); + images.addEventListener("touch", _close); + }, 300); + images.removeEventListener("mouseup", _open); + images.removeEventListener("touchend", _open); + } + }; + + const _close = () => { + if (typeof images.hotline === "object") { + images.hotline.start(); + } + + images.classList.remove("extend"); + + if (button) core.telegram.api.MainButton.show(); + + images.removeEventListener("click", _close); + images.removeEventListener("touch", _close); + images.addEventListener("mousedown", _start); + images.addEventListener("touchstart", _start); + }; + + const _start = (event) => { + if ( + event.type === "touchstart" || + event.button === 0 + ) { + images.removeEventListener("mousedown", _start); + images.removeEventListener("touchstart", _start); + images.addEventListener("mouseup", _open); + images.addEventListener("touchend", _open); + } + }; + + images.addEventListener("mousedown", _start); + images.addEventListener("touchstart", _start); + for (const uri of json.product.images) { const image = document.createElement("img"); image.setAttribute("src", uri); image.setAttribute("ondragstart", "return false;"); - const button = core.telegram.api.isVisible; - - const open = (event) => { - if (event.target === from) { - if (typeof images.hotline === "object") { - if (images.hotline.moving) return; - images.hotline.stop(); - } - - image.classList.add("extend"); - - if (button) core.telegram.api.MainButton.hide(); - - setTimeout(() => { - image.addEventListener("click", close); - image.addEventListener("touch", close); - }, 300); - image.removeEventListener("mouseup", open); - image.removeEventListener("touchend", open); - } - }; - - const close = () => { - if (typeof images.hotline === "object") { - images.hotline.start(); - } - - image.classList.remove("extend"); - - if (button) core.telegram.api.MainButton.show(); - - image.removeEventListener("click", close); - image.removeEventListener("touch", close); - image.addEventListener("mousedown", start); - image.addEventListener("touchstart", start); - }; - - const start = (event) => { - if ( - event.type === "touchstart" || - event.button === 0 - ) { - image.removeEventListener("mousedown", start); - image.removeEventListener("touchstart", start); - image.addEventListener("mouseup", open); - image.addEventListener("touchend", open); - } - }; - - image.addEventListener("mousedown", start); - image.addEventListener("touchstart", start); - images.append(image); } @@ -539,7 +554,7 @@ import("/js/core.js").then(() => const cost = document.createElement("p"); cost.classList.add("cost"); cost.innerText = json.product.cost + "р"; - + h3.append(title); h3.append(brand); card.append(h3); diff --git a/mirzaev/arming_bot/system/public/js/damper.js b/mirzaev/arming_bot/system/public/js/damper.js index f0acea7..441d921 100755 --- a/mirzaev/arming_bot/system/public/js/damper.js +++ b/mirzaev/arming_bot/system/public/js/damper.js @@ -9,7 +9,10 @@ import("/js/core.js").then(() => { initialization(); } }, 10); - const timeout = setTimeout(() => clearInterval(dependencies), 5000); + const timeout = setTimeout(() => { + clearInterval(dependencies); + initialization(); + }, 5000); function initialization() { if (typeof core.damper === "undefined") { diff --git a/mirzaev/arming_bot/system/public/js/hotline.js b/mirzaev/arming_bot/system/public/js/hotline.js index 3871e7c..2abafdb 100644 --- a/mirzaev/arming_bot/system/public/js/hotline.js +++ b/mirzaev/arming_bot/system/public/js/hotline.js @@ -9,7 +9,10 @@ import("/js/core.js").then(() => { initialization(); } }, 10); - const timeout = setTimeout(() => clearInterval(dependencies), 5000); + const timeout = setTimeout(() => { + clearInterval(dependencies); + initialization(); + }, 5000); function initialization() { if (typeof core.hotline === "undefined") { diff --git a/mirzaev/arming_bot/system/public/js/session.js b/mirzaev/arming_bot/system/public/js/session.js index 587ca5a..c659ee5 100755 --- a/mirzaev/arming_bot/system/public/js/session.js +++ b/mirzaev/arming_bot/system/public/js/session.js @@ -4,14 +4,19 @@ import("/js/core.js").then(() => import("/js/damper.js").then(() => { const dependencies = setInterval(() => { - if (typeof core === "function" && - typeof core.damper === "function") { + if ( + typeof core === "function" && + typeof core.damper === "function" + ) { clearInterval(dependencies); clearTimeout(timeout); initialization(); } }, 10); - const timeout = setTimeout(() => clearInterval(dependencies), 5000); + const timeout = setTimeout(() => { + clearInterval(dependencies); + initialization(); + }, 5000); function initialization() { if (typeof core.session === "undefined") { diff --git a/mirzaev/arming_bot/system/public/js/telegram.js b/mirzaev/arming_bot/system/public/js/telegram.js index 976b66b..332d206 100755 --- a/mirzaev/arming_bot/system/public/js/telegram.js +++ b/mirzaev/arming_bot/system/public/js/telegram.js @@ -13,7 +13,10 @@ import("/js/core.js").then(() => initialization(); } }, 10); - const timeout = setTimeout(() => clearInterval(dependencies), 5000); + const timeout = setTimeout(() => { + clearInterval(dependencies); + initialization(); + }, 5000); function initialization() { if (typeof core.telegram === "undefined") { diff --git a/mirzaev/arming_bot/system/public/robot.php b/mirzaev/arming_bot/system/public/robot.php index d36026f..2150b84 100755 --- a/mirzaev/arming_bot/system/public/robot.php +++ b/mirzaev/arming_bot/system/public/robot.php @@ -7,16 +7,16 @@ namespace mirzaev\arming_bot; // Files of the project use mirzaev\arming_bot\controllers\core as controller, mirzaev\arming_bot\models\core as model, - mirzaev\arming_bot\models\chat; + mirzaev\arming_bot\models\telegram; // Фреймворк Telegram use Zanzara\Zanzara, Zanzara\Context, Zanzara\Config; -/* ini_set('error_reporting', E_ALL); +ini_set('error_reporting', E_ALL); ini_set('display_errors', 1); -ini_set('display_startup_errors', 1); */ +ini_set('display_startup_errors', 1); // Версия робота define('ROBOT_VERSION', '1.0.0'); @@ -62,27 +62,27 @@ $bot = new Zanzara(KEY, $config); var_dump($ctx->getEffectiveUser() ); }); */ -$bot->onCommand('start', fn($ctx) => chat::start($ctx)); -$bot->onCommand('contacts', fn($ctx) => chat::contacts($ctx)); -$bot->onCommand('company', fn($ctx) => chat::company($ctx)); -$bot->onCommand('community', fn($ctx) => chat::community($ctx)); -$bot->onCommand('settings', fn($ctx) => chat::settings($ctx)); +$bot->onCommand('start', fn($ctx) => telegram::start($ctx)); +$bot->onCommand('contacts', fn($ctx) => telegram::contacts($ctx)); +$bot->onCommand('company', fn($ctx) => telegram::company($ctx)); +$bot->onCommand('community', fn($ctx) => telegram::community($ctx)); +$bot->onCommand('settings', fn($ctx) => telegram::settings($ctx)); -$bot->onText('💬 Контакты', fn($ctx) => chat::contacts($ctx)); -$bot->onText('🏛️ О компании', fn($ctx) => chat::company($ctx)); -$bot->onText('🎯 Сообщество', fn($ctx) => chat::community($ctx)); -$bot->onText('⚙️ Настройки', fn($ctx) => chat::settings($ctx)); +$bot->onText('💬 Контакты', fn($ctx) => telegram::contacts($ctx)); +$bot->onText('🏛️ О компании', fn($ctx) => telegram::company($ctx)); +$bot->onText('🎯 Сообщество', fn($ctx) => telegram::community($ctx)); +$bot->onText('⚙️ Настройки', fn($ctx) => telegram::settings($ctx)); -$bot->onCbQueryData(['mail'], fn($ctx) => chat::_mail($ctx)); -$bot->onCbQueryData(['import_request'], fn($ctx) => chat::import_request($ctx)); -$bot->onCbQueryData(['tuning'], fn($ctx) => chat::tuning($ctx)); -$bot->onCbQueryData(['brands'], fn($ctx) => chat::brands($ctx)); +$bot->onCbQueryData(['mail'], fn($ctx) => telegram::_mail($ctx)); +$bot->onCbQueryData(['import_request'], fn($ctx) => telegram::import_request($ctx)); +$bot->onCbQueryData(['tuning'], fn($ctx) => telegram::tuning($ctx)); +$bot->onCbQueryData(['brands'], fn($ctx) => telegram::brands($ctx)); // Инициализация middleware с обработкой аккаунта -$bot->middleware([chat::class, "account"]); +$bot->middleware([telegram::class, "account"]); // Инициализация middleware с обработкой технических работ разных уровней -$bot->middleware([chat::class, "suspension"]); +$bot->middleware([telegram::class, "suspension"]); // Запуск чат-робота $bot->run(); diff --git a/mirzaev/arming_bot/system/public/themes/default/css/catalog/2columns.css b/mirzaev/arming_bot/system/public/themes/default/css/catalog/2columns.css index ed53644..b837e1c 100644 --- a/mirzaev/arming_bot/system/public/themes/default/css/catalog/2columns.css +++ b/mirzaev/arming_bot/system/public/themes/default/css/catalog/2columns.css @@ -8,21 +8,60 @@ main>section[data-section="catalog"] { } main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"] { + position: relative; height: 23px; - padding: 8px 16px; + padding: unset; display: flex; justify-content: center; align-items: center; flex-grow: 1; + overflow: hidden; border-radius: 0.75rem; color: var(--tg-theme-button-text-color); background-color: var(--tg-theme-button-color); } + main>section[data-section="catalog"][data-catalog-type="categories"]:last-child { /* margin-bottom: unset; */ } +main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]:has(>img) { + height: 180px; +} + +main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]>img { + position: absolute; + left: -5%; + top: -5%; + width: 110%; + height: 110%; + object-fit: cover; + filter: blur(1px); +} + +main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]:hover>img { + filter: unset; +} + +main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]:has(>img)>p { + --padding: 0.7rem; + position: absolute; + left: var(--padding); + bottom: var(--padding); + right: var(--padding); + margin: unset; + width: min-content; + padding: var(--padding); + border-radius: 0.75rem; + background: var(--tg-theme-secondary-bg-color); +} + +main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]>p { + z-index: 100; + padding: 8px 16px; +} + main>section[data-section="catalog"][data-catalog-type="products"] { --column: calc((100% - var(--gap)) / 2); width: var(--width); @@ -120,3 +159,21 @@ main>section[data-section="cart"]>i.icon.shopping.cart { top: -1px; color: var(--tg-theme-button-text-color); } + +main>section[data-section="filters"] { + --diameter: 4rem; + z-index: 999; + right: 5vw; + bottom: 5vw; + position: fixed; + width: var(--diameter); + height: var(--diameter); + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + border-radius: 100%; + background-color: var(--tg-theme-button-color); +} + +main>section[data-section="filters"][data-filter="brand"] {} diff --git a/mirzaev/arming_bot/system/public/themes/default/css/icons/hashtag.css b/mirzaev/arming_bot/system/public/themes/default/css/icons/hashtag.css new file mode 100755 index 0000000..b1da3b9 --- /dev/null +++ b/mirzaev/arming_bot/system/public/themes/default/css/icons/hashtag.css @@ -0,0 +1,26 @@ +@charset "UTF-8"; + +i.icon.hashtag { + box-sizing: border-box; + position: relative; + display: block; + transform: scale(1); + width: 8px; + height: 16px; + border-left: 2px solid; + border-right: 2px solid; +} + +i.icon.hashtag::before { + content: ""; + display: block; + box-sizing: border-box; + position: absolute; + width: 16px; + height: 8px; + border-top: 2px solid; + border-bottom: 2px solid; + left: -6px; + top: 4px; +} + diff --git a/mirzaev/arming_bot/system/public/themes/default/css/window.css b/mirzaev/arming_bot/system/public/themes/default/css/window.css index 8c1c643..696c46d 100755 --- a/mirzaev/arming_bot/system/public/themes/default/css/window.css +++ b/mirzaev/arming_bot/system/public/themes/default/css/window.css @@ -50,6 +50,21 @@ section#window>div.card>div.images { overflow: clip; } +section#window>div.card>div.images.extend { + z-index: 9999999; + left: 0; + top: 0; + margin: unset !important; + position: absolute; + width: 100vw; + max-width: unset; + height: 100vh; + object-fit: contain; + border-radius: unset; + transition: 0s; + cursor: zoom-out; +} + section#window>div.card>div.images>img { margin-right: 0.5rem; width: 10rem; diff --git a/mirzaev/arming_bot/system/storage/.gitignore b/mirzaev/arming_bot/system/storage/.gitignore deleted file mode 100755 index c993200..0000000 --- a/mirzaev/arming_bot/system/storage/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -!example.xlsx -import.xlsx diff --git a/mirzaev/arming_bot/system/storage/categories/.gitignore b/mirzaev/arming_bot/system/storage/categories/.gitignore new file mode 100644 index 0000000..593bcf0 --- /dev/null +++ b/mirzaev/arming_bot/system/storage/categories/.gitignore @@ -0,0 +1,2 @@ +!.gitignore +* diff --git a/mirzaev/arming_bot/system/storage/import/.gitignore b/mirzaev/arming_bot/system/storage/import/.gitignore new file mode 100644 index 0000000..593bcf0 --- /dev/null +++ b/mirzaev/arming_bot/system/storage/import/.gitignore @@ -0,0 +1,2 @@ +!.gitignore +* diff --git a/mirzaev/arming_bot/system/storage/products/.gitignore b/mirzaev/arming_bot/system/storage/products/.gitignore new file mode 100644 index 0000000..593bcf0 --- /dev/null +++ b/mirzaev/arming_bot/system/storage/products/.gitignore @@ -0,0 +1,2 @@ +!.gitignore +* diff --git a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/categories.html b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/categories.html index 6c1fbd8..8a37554 100755 --- a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/categories.html +++ b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/categories.html @@ -3,8 +3,10 @@ data-catalog-level="{{ level ?? 0 }}"> {% for category in categories %} {{ - category.label.ru }} + data-category-identifier="{{ category.identifier }}"> + {{ category.name[ account.language ?? settings.language ?? 'en' ] }} +

{{ category.name[ account.language ?? settings.language ?? 'en' ] }}

+ {% endfor %} {% endif %} diff --git a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/filters.html b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/filters.html new file mode 100755 index 0000000..ca2cdfc --- /dev/null +++ b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/filters.html @@ -0,0 +1,8 @@ +{% if filters is not empty %} +
+ {% for brand in filters.brands %} + + + {% endfor %} +
+{% endif %} diff --git a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/products/2columns.html b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/products/2columns.html index 3ae20cb..f1b7b92 100755 --- a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/products/2columns.html +++ b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/products/2columns.html @@ -3,7 +3,7 @@ ~ product.dimensions.z ~ ' ' ~ product.weight ~ 'г' %}
- {{ product.title.ru }} + {{ product.title.ru }}

{{ title | length > 45 ? title | slice(0, 45) ~ '...' : title }}

diff --git a/mirzaev/arming_bot/system/views/themes/default/catalog/page.html b/mirzaev/arming_bot/system/views/themes/default/catalog/page.html index 2f0e9bb..d56851a 100755 --- a/mirzaev/arming_bot/system/views/themes/default/catalog/page.html +++ b/mirzaev/arming_bot/system/views/themes/default/catalog/page.html @@ -14,6 +14,7 @@ {% include "/themes/default/catalog/elements/categories.html" %} {% include "/themes/default/catalog/elements/products/2columns.html" %} +{% include "/themes/default/catalog/elements/filters.html" %} {% endblock %} {% block js %}