import from excel + broken dependencies see next commit

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2024-10-11 09:58:35 +03:00
parent f69d6ae96c
commit af48d505bf
43 changed files with 2090 additions and 1410 deletions

View File

@ -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 ~ /(?<type>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,

View File

@ -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": {

509
composer.lock generated
View File

@ -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",

View File

@ -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
]

View File

@ -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;

View File

@ -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;

View File

@ -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 <arsen@mirzaev.sexy>
*/
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;
}
}

View File

@ -0,0 +1,487 @@
<?php
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
// Files of the project
use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\product,
mirzaev\arming_bot\models\category,
mirzaev\arming_bot\models\entry,
mirzaev\arming_bot\models\traits\files,
mirzaev\arming_bot\models\traits\yandex\disk as yandex;
// Framework for ArangoDB
use mirzaev\arangodb\collection,
mirzaev\arangodb\document;
// Framework for Excel
use avadim\FastExcelReader\Excel as excel;
// Built-in libraries
use exception;
/**
* Model of the catalog
*
* @package mirzaev\arming_bot\models
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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()
];
}
}
}

View File

@ -1,294 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
// Files of the project
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;
// Library для ArangoDB
use ArangoDBClient\Document as _document;
// Built-in libraries
use exception;
/**
* Model of a category
*
* @package mirzaev\arming_bot\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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(
<<<AQL
FOR v IN 1..1 INBOUND %s GRAPH hierarchy
%s
%s
LIMIT %u, %u
LET _type = (FOR v2 IN INBOUND v._id GRAPH hierarchy RETURN v2)[0] ? "category" : "product"
RETURN MERGE(v, {_type})
AQL,
empty($category) ? '(FOR d IN ' . ${static::COLLECTION} . 'LIMIT 1 RETURN d._id)[0]' : "\"$category\"",
empty($filter) ? '' : "FILTER $filter",
empty($sort) ? '' : "SORT $sort",
--$page <= 0 ? $page = 0 : $page * $amount,
$amount,
)
)) {
// Найдены вхождения
// Возврат (успех)
return is_array($documents) ? $documents : [$documents];
} else return [];
} else throw new exception('Failed to initialize document collection: ' . static::COLLECTION);
} catch (exception $e) {
// Write to the registry of errors
$errors[] = [
'text' => $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(
<<<AQL
FOR e IN %s
COLLECT AGGREGATE
from_category = UNIQUE(PARSE_IDENTIFIER(e._from).collection),
to_category = UNIQUE(PARSE_IDENTIFIER(e._to).collection)
RETURN UNIQUE(UNION(from_category, to_category))
AQL,
$collection
)
)) {
// Найдены коллекции
// Возврат (успех)
return $names->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(
<<<AQL
FOR u IN UNION(%s)
FILTER u.name == "%s"
SORT u.created ASC
LIMIT 1
RETURN u
AQL,
implode(', ', $union),
$name
)
)) {
// Найдена категория
// Инициализация названия коллекции
$collection = explode('/', $document->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;
}
}

View File

@ -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 <arsen@mirzaev.sexy>
*/
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;
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
// Files of the project
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\enumerations\collection\type;
/**
* Model of connect
*
* @package mirzaev\arming_bot\models
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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;
}

View File

@ -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 <arsen@mirzaev.sexy>
*/
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

View File

@ -0,0 +1,318 @@
<?php
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
// Files of the project
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;
// Library for ArangoDB
use ArangoDBClient\Document as _document;
// Framework for ArangoDB
use mirzaev\arangodb\collection,
mirzaev\arangodb\document,
mirzaev\arangodb\enumerations\collection\type;
// Built-in libraries
use exception;
/**
* Model of entry
*
* @package mirzaev\arming_bot\models
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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;
}
}

View File

@ -1,22 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
// Files of the project
use mirzaev\arming_bot\models\categories;
/**
* Model of a part
*
* @package mirzaev\arming_bot\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class part extends categories
{
/**
* Name of the collection in ArangoDB
*/
final public const string COLLECTION = 'part';
}

View File

@ -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 <arsen@mirzaev.sexy>
*/
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(
<<<AQL
FOR d IN %s
AQL,
$search ? self::COLLECTION . 's_search' : self::COLLECTION
);
if ($search) {
// Search
$aql .= sprintf(
<<<AQL
SEARCH
LEVENSHTEIN_MATCH(
d.title.ru,
TOKENS("%s", "text_ru")[0],
1,
false
) OR
levenshtein_match(
d.description.ru,
tokens("%s", "text_ru")[0],
1,
false
) OR
levenshtein_match(
d.compatibility.ru,
tokens("%s", "text_ru")[0],
1,
false
)
AQL,
$search,
$search,
$search
);
// Adding sorting
if ($sort) $sort = "BM25(d) DESC, $sort";
else $sort = "BM25(d) DESC";
}
if ($documents = collection::search(
core::$arangodb->session,
sprintf(
$aql . <<<AQL
%s
%s
LIMIT %u, %u
RETURN %s
AQL,
empty($filter) ? '' : "FILTER $filter",
empty($sort) ? '' : "SORT $sort",
--$page <= 0 ? $page = 0 : $page * $amount,
$amount,
empty($return) ? 'd' : $return
)
)) {
// Найдены товары
// Возврат (успех)
return is_array($documents) ? $documents : [$documents];
} else return [];
} else throw new exception('Failed to initialize document collection: ' . self::COLLECTION);
} catch (exception $e) {
// Write to the registry of errors
$errors[] = [
'text' => $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 [];
}
}

View File

@ -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 <arsen@mirzaev.sexy>
*/
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(
<<<AQL
FOR d IN %s
FILTER d._id == '%s' && d.expires > %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(
<<<AQL
FOR v IN INBOUND "%s" GRAPH sessions
SORT v.created DESC
LIMIT 1
RETURN v
AQL,
$this->getId(),
)
)) {
// Найден аккаунт
// Search for connected account
$document = collection::execute(
<<<AQL
FOR v IN INBOUND @session GRAPH sessions
SORT v.created DESC
LIMIT 1
RETURN v
AQL,
[
'session' => $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(
<<<AQL
FOR d IN %s
FILTER d.hash == '%s' && d.expires > %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(
<<<AQL
FOR d IN %s
FILTER d.address == '%s' && d.expires > %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;
}
}

View File

@ -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 <arsen@mirzaev.sexy>
*/
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;
}
}

View File

@ -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 <arsen@mirzaev.sexy>
*/
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(),

View File

@ -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 <arsen@mirzaev.sexy>
*/
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(<<<TXT
*Загружено для обработки:* $loaded
$ctx->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(<<<TXT
🏷 *Категории*
*Загружено:* $categories_loaded
*Добавлено:* $categories_created
*Обновлено:* $categories_updated
*Удалено:* $categories_deleted
*Было:* $categories_old
*Стало:* $categories_new
TXT)
->then(function ($message) use ($ctx, $products_loaded, $products_created, $products_updated, $products_deleted, $products_old, $products_new) {
$ctx->sendMessage(<<<TXT
📦 *Товары*
*Загружено:* $products_loaded
*Добавлено:* $products_created
*Обновлено:* $products_updated
*Удалено:* $products_deleted
*Было:* $products_old
*Стало:* $products_new
TXT)
->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);
}
}

View File

@ -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 <arsen@mirzaev.sexy>
*/
@ -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;
}
}

View File

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace mirzaev\arming_bot\models\traits;
// Built-in libraries
use exception;
/**
* Trait for initialization of files handlers
*
* @package mirzaev\arming_bot\models\traits
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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;
}
}

View File

@ -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(),

View File

@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace mirzaev\arming_bot\models\traits\yandex;
// Built-in libraries
use exception;
/**
* Trait for "Yandex Disk"
*
* @package mirzaev\arming_bot\models\traits\yandex
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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;
}
}

View File

@ -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');

View File

@ -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(() => {

View File

@ -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") {

View File

@ -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);
}

View File

@ -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") {

View File

@ -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") {

View File

@ -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") {

View File

@ -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") {

View File

@ -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();

View File

@ -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"] {}

View File

@ -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;
}

View File

@ -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;

View File

@ -1,2 +0,0 @@
!example.xlsx
import.xlsx

View File

@ -0,0 +1,2 @@
!.gitignore
*

View File

@ -0,0 +1,2 @@
!.gitignore
*

View File

@ -0,0 +1,2 @@
!.gitignore
*

View File

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

View File

@ -0,0 +1,8 @@
{% if filters is not empty %}
<section data-section="filters" data-filter="brand">
{% for brand in filters.brands %}
<input class="menu" name="brand" type="radio" id="brand_{{ loop.index }}" checked>
<label for="brand_{{ loop.index }}" class="menu option">{{ brand }}</label>
{% endfor %}
</section>
{% endif %}

View File

@ -3,7 +3,7 @@
~ product.dimensions.z ~ ' ' ~ product.weight ~ 'г' %}
<article id="{{ product.getId() }}" class="product unselectable">
<a onclick="core.catalog.product({{ product.getKey() }})">
<img src="/images/{{ product.getKey() }}/1.jpg" alt="{{ product.title.ru }}" ondrugstart="return false;">
<img src="{{ product.images.0.storage }}" alt="{{ product.title.ru }}" ondrugstart="return false;">
<p class="title" title="{{ product.title.ru }}">
{{ title | length > 45 ? title | slice(0, 45) ~ '...' : title }}
</p>

View File

@ -14,6 +14,7 @@
{% include "/themes/default/catalog/elements/categories.html" %}
{% include "/themes/default/catalog/elements/products/2columns.html" %}
<!-- {% include "/themes/default/catalog/elements/cart.html" %} -->
{% include "/themes/default/catalog/elements/filters.html" %}
{% endblock %}
{% block js %}