Compare commits

...

23 Commits

Author SHA1 Message Date
Arsen Mirzaev Tatyano-Muradovich 86ad3f5bfc resolved #40 2025-04-15 16:31:04 +03:00
Arsen Mirzaev Tatyano-Muradovich 80d3b430ef nginx fix 2025-04-10 17:59:25 +03:00
Arsen Mirzaev Tatyano-Muradovich b3f6f15750 update for nginx and systemd 2025-04-10 21:52:49 +07:00
Arsen Mirzaev Tatyano-Muradovich 430715c792 the systemd section improved 2025-04-10 21:14:24 +07:00
Arsen Mirzaev Tatyano-Muradovich ec03c8218c readme + css 2025-04-10 17:02:10 +03:00
Arsen Mirzaev Tatyano-Muradovich 5382e15af2 resolved #9, resolved #15, resolved #17, resolved #22, resolved #36, resolved #37, resolved #38, resolved #39 2025-04-03 22:18:23 +03:00
Arsen Mirzaev Tatyano-Muradovich 7847d15da1 resolved #31, resolved #32, resolved #33, resolved #35 2025-03-07 09:55:15 +03:00
Arsen Mirzaev Tatyano-Muradovich 10af91a081 resolved #26, resolved #30 2025-02-13 11:54:03 +03:00
Arsen Mirzaev Tatyano-Muradovich 61730cc1f2 resolved #26 2025-02-13 10:38:37 +03:00
Arsen Mirzaev Tatyano-Muradovich a12b736dba resolved #28, resolved #29 2025-02-13 08:54:43 +03:00
Arsen Mirzaev Tatyano-Muradovich b91968606a resolved #28 2025-02-13 08:40:08 +03:00
Arsen Mirzaev Tatyano-Muradovich cb22d1b082 resolved #26, resolved #27 2025-02-13 07:06:13 +03:00
Arsen Mirzaev Tatyano-Muradovich 9296e14477 fix markdown from images to links 2025-01-28 14:27:51 +07:00
Arsen Mirzaev Tatyano-Muradovich 4089f12dc6 removed EOL and changed order of dependencies 2025-01-28 13:37:53 +07:00
Arsen Mirzaev Tatyano-Muradovich 405170b5b2 added list of projects 2025-01-16 14:35:39 +07:00
Arsen Mirzaev Tatyano-Muradovich d0930fd73a fixed for github 2025-01-16 14:27:27 +07:00
Arsen Mirzaev Tatyano-Muradovich 19a797cdf3 added "the" 2025-01-16 14:04:25 +07:00
Arsen Mirzaev Tatyano-Muradovich db9632cc72 added integrations 2025-01-16 13:59:59 +07:00
Arsen Mirzaev Tatyano-Muradovich 9e31e692c6 added functions and updated the description 2025-01-16 12:29:36 +07:00
Arsen Mirzaev Tatyano-Muradovich fdea6c8408 added dependencies 2025-01-16 09:42:29 +07:00
Arsen Mirzaev Tatyano-Muradovich 64f1a585f9 Fixes and added `menu` section 2025-01-15 13:20:11 +07:00
Arsen Mirzaev Tatyano-Muradovich 58da87bb0e menu fixes 2025-01-15 09:18:58 +03:00
Arsen Mirzaev Tatyano-Muradovich 3b32fc10b5 renamed from arming_bot and fixes 2025-01-14 22:11:54 +03:00
248 changed files with 10330 additions and 4964 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
node_modules
vendor

324
README.md
View File

@ -1,51 +1,158 @@
# huesos
Basis for developing chat-robots with "Web App" technology for Telegram
Base for creating shop chat-robots using Web App technology for [Telegram](https://telegram.org)
## Functions
1. Tree-structured catalog
2. Product cards with images carousel ([mirzaev/hotline.mjs](https://git.svoboda.works/mirzaev/hotline.mjs))
3. Cart (CRUD with limits and session binding)
4. Saving user data (and session) for all devices
5. Deliveries settings (with interactive maps and automatic geolocation detection on smartphones)
6. Real time price generation
7. Interface according to all Telegram standards
8. Public offer, dynamic settings and suspensions
9. Multi-language and easy to add new languages
10. Multi-currency and easy to add new currencies
11. Loading products and categories from an excel-file with automatic updating of existing ones
12. Flag authorization system, separate access for testers
13. Sending the generated order directly to the chat-robot
14. Intelligent search by titles, descriptions and other parameters (Levenshtein algorithm + separate settings for different languages)
15. Asynchronous chat-robot and Web App based on dynamic queries (AJAX)
16. Modern non-relational database ready for scaling and integration with third-party CRM
17. Fully documented code in English
18. Customizable menu buttons
19. Responsive design with built-in Telegram buttons and haptic functions
20. Automatic download and compression of images in 4 sizes (currently only from Yandex.Disk, but the system is ready to add new sources)
21. Commercially approved fonts and pure CSS icons
22. Product filter panel using pure CSS
23. Damper technology on all user interaction functions ([mirzaev/damper.mjs](https://git.svoboda.works/mirzaev/damper.mjs))
24. Two-step registration system (entering other data after creating an order)
25. Delivery company selection system (ready for scaling)
26. Acquiring company selection system (ready for scaling)
27. Sending paid orders to the operators chat with the customer contacts
## Integrations
### Import
*Methods for importing products into the shop*<br>
1. Excel-file (products and categories)
### Images download
*Methods of transferring images when importing products into the shop*<br>
1. [Yandex.Disk](https://360.yandex.ru/disk/) (russian) ([API](https://yandex.com/dev/disk/))
### Delivery companies
*Companies that deliver products from the shop*<br>
1. [CDEK](https://www.cdek.ru/) (russian) ([API](https://api-docs.cdek.ru/29923741.html)) ([PHP library](https://github.com/TTATPuOT/cdek-sdk2.0))
### Acquiring companies
*Companies that provide acquiring for the shop*<br>
1. [Robokassa](https://robokassa.com) (russian) (no swift) ([API](https://docs.robokassa.ru/pay-interface/))
## Dependencies
1. [PHP 8.4](https://www.php.net/releases/8.4/en.php)
2. [Composer](https://getcomposer.org/) (php package manager)
3. [MINIMAL](https://git.svoboda.works/mirzaev/minimal) (PHP framework)
4. [Twig](https://twig.symfony.com/) (HTML templater)
5. [Zanzara](https://github.com/badfarm/zanzara) (Telegram framework + ReactPHP)
6. [ArangoDB](https://docs.arangodb.com/3.11/about-arangodb/) (non-relational database)
7. [NGINX](https://nginx.org/en/) (web server) *(can be replaced)*
8. [SystemD](https://systemd.io/) (service manager) *(can be replaced)*
<small>You can find other dependencies in the file `/composer.json`</small>
## Installation
### AnangoDB
1. Create a Graph with the specified values
**Name:** catalog<br>
1. **Configure unix-socket**<br>
Edit the file `/etc/arangodb3/arangod.conf`<br>
`endpoint = tcp://127.0.0.1:8529` -> `endpoint = unix:///var/run/arangodb3/arango.sock` (this will disable the web panel)<br>
<br>
* Relatin 1
To make the web panel work, you can add this to the NGINX server settings:
```lua
server {
...
server_name arangodb.domain.zone;
...
allow YOUR_IP_ADDRESS;
allow 192.168.1.1/24;
allow 127.0.0.1;
deny all;
# ArangoDB
location / {
proxy_pass http://arangodb;
}
}
upstream arangodb {
server unix:/var/run/arangodb3/arango.sock;
}
```
[here is my solution for "permission denied" problem on Ubuntu (accepted by ArangoDB maintainer)](https://github.com/arangodb/arangodb/issues/17302)<br>
1. **Configure TCP (instead of unix-socket)**<br>
Edit the file `/etc/arangodb3/arangod.conf`<br>
`endpoint = tcp://127.0.0.1:8529` -> `endpoint = tcp://0.0.0.0:8529`<br>
Edit the file `mirzaev/huesos/system/settings/arangodb.php`<br>
`unix:///var/run/arangodb3/arango.sock` -> `tcp://YOUR_IP_ADDRESS:8529` (it is slow and not secure)
---
2. **Create a Graph with the specified values**<br>
**Name:** catalog<br>
* Relation 1<br>
**edgeDefinition:** entry<br>
**fromCollections:** categoy, product<br>
**fromCollections:** category, product<br>
**toCollections:** category
* Relation 2
* Relation 2<br>
**edgeDefinition:** reservation<br>
**fromCollections:** product<br>
**toCollections:** cart
2. Create a Graph with the specified values
---
3. **Create a Graph with the specified values**<br>
**Name:** users<br>
<br>
* Relation 1
* Relation 1<br>
**edgeDefinition:** connect<br>
**fromCollections:** cart, session<br>
**toCollections:** account, session
**toCollections:** account, session<br>
**Orphan Collections:** product
* Orphan Collections<br>
product
3. Create indexes for the "product" collection
---
4. **Create indexes for the "product" collection**<br>
**Type:** "Inverted Index"<br>
**Fields:** name.ru<br>
**Analyzer:** "text_ru"<br>
**Search field:** true<br>
**Name:** name_ru<br>
<br>
**Name:** name_ru<br><br>
*Add indexes for all search parameters and for all languages (search language is selected based on the user's language, <br>
otherwise from the default language specified in the active settings from **settings** collection document)*<br>
<br>
*See fields in the `mirzaev/arming_bot/models/product`<br>
**name.ru**, **description.ru** and **compatibility.ru***
**name.ru**, **description.ru** and **compatibility.ru***<br>
4. Create a View with the specified values
---
5. **Create a View with the specified values**<br>
**type:** search-alias (you can also use "arangosearch")<br>
**name:** **product**s_search<br>
**indexes:**<br>
<br>
**indexes:**<br><br>
You can copy an example of view file from here: `/examples/arangodb/views/products_search.json`
```json
@ -59,50 +166,177 @@ You can copy an example of view file from here: `/examples/arangodb/views/produc
### NGINX
1. Create a NGINX server
1. **Create a NGINX server**<br>
You can copy an example of server file from here: `/examples/nginx/server.conf`
2. Add support for javascript modules
2. **Add support for javascript modules**<br>
Edit the file `/etc/nginx/mime.types`<br>
`application/javascript js;` -> `application/javascript js mjs;`
3. **Generate a TLS/SSL sertificate** (via [certbot](http://certbot.eff.org/) for [ubuntu](https://ubuntu.com/))<br>
3.1. `sudo apt install certbot python3-certbot-nginx`<br>
3.2. `sudo certbot certonly --nginx` (The **domain** must already be **bound** to the **IP-address** of the server by `A-record` or `AAAA-record`)
5. **Firewall rules for HTTP and HTTPS** (for [ubuntu](https://ubuntu.com/))<br>
4.1 `sudo ufw allow "NGINX Full"`<br>
4.1.1 `sudo ufw allow 22` (make sure that the port for SSH connection is open)<br>
4.2 `sudo ufw enable`
### SystemD (or any alternative you like)
You can copy an example of systemd file from here: `/examples/systemd/huesos.service`<br>
<br>
**Execute:** `sudo cp huesos.service /etc/systemd/system/huesos.service && sudo chmod +x /etc/systemd/system/huesos.service`<br>
<br>
1. `sudo cp huesos.service /etc/systemd/system/huesos.service && sudo chmod +x /etc/systemd/system/huesos.service`
2. `sudo systemctl daemon-reload`
3. `sudo systemctl enable huesos`<br>
*before you execute the command think about **what it does** and whether the **paths** are specified correctly*<br>
*the configuration file is very simple and you can remake it for any alternative to SystemD that you like*
## Menu
*Menu inside the Web App*<br><br>
Make sure you have a **menu** collection (can be created automatically)<br>
You can copy a clean menu documents without comments from here: `/examples/arangodb/collections/menu`
```json
{
"urn": "/", // Link
"name": {
"en": "Main page",
"ru": "Главная страница"
},
"style": { // The `style` attribute
"order": 0
},
"class": "",
"icon": { // Icon from `/themes/default/css/icons`
"style": { // The `style` attribute
"rotate": "-135deg"
},
"class": "arrow circle" // Classes of the icon
},
"image": { // Image at the background @deprecated?
"storage": null
}
}
```
## Settings
Settings of chat-robot and Web App<br>
<br>
*Settings of chat-robot and Web App*<br><br>
Make sure you have a **settings** collection (can be created automatically) and at least one document with the "status" parameter set to "active"<br>
You can copy a clean settings document without comments from here: `/examples/arangodb/collections/settings.json`
```json
{
"status": "active",
"project": {
"name": "PROJECT"
},
"language": "en", // Will be converted to an instance of enumeration `mirzaev\arming_bot\models\enumerations\language`
"currency": "usd", // Will be converted to an instance of enumeration `mirzaev\arming_bot\models\enumerations\currency`
"company": {
"identifier": null, // Example: "000000000000000" (string|null) (if `null` it will not be displayed)
"tax": null, // Example: "000000000000" (string|null) (if `null` it will not be displayed)
"name": null, // Example: "COMPANY" (string|null) (if `null` it will not be displayed)
"offer": false, // Display the data of a public offer in the footer? (bool) (does not affect the `/offer` page generation)
"sim": null, // Examples: "+7 000 000-00-00", "70000000000" (string|null) (if `null` it will not be displayed)
"mail": null // Example: "name@domain.zone" (string|null) (if `null` it will not be displayed)
"status": "active", // Values: "active", "inactive" (string) Status of the settings document?
"project": {
"name": "PROJECT" // Name of the projext (string)
},
"language": "en", // Will be converted to an instance of enumeration `mirzaev\arming_bot\models\enumerations\language`
"currency": "usd", // Will be converted to an instance of enumeration `mirzaev\arming_bot\models\enumerations\currency`
"company": {
"identifier": null, // Example: "000000000000000" (string|null) (if `null` it will not be displayed)
"tax": null, // Example: "000000000000" (string|null) (if `null` it will not be displayed)
"name": null, // Example: "COMPANY" (string|null) (if `null` it will not be displayed)
"offer": false, // Display the data of a public offer in the footer? (bool) (does not affect the `/offer` page generation)
"sim": null, // Examples: "+7 000 000-00-00", "70000000000" (string|null) (if `null` it will not be displayed)
"mail": null // Example: "name@domain.zone" (string|null) (if `null` it will not be displayed)
},
"search": {
"enabled": true, // Enable the search input field?
"position": "fixed" // Values: "fixed", "relative"
},
"catalog": {
"categories": {
"display": "column", // Values: "row" (flex wrap), "column" (rows) Type of the catalog display
"structure": "lists", // Values: "pages" (pages with categories and products), "lists" (all categories as tree lists on the main page)
"buttons": {
"height": "120px", // Examples: "80px", "120px", "180px" (string|null) Height of buttons
"background": "#fafafa", // Examples: "#fafafa", "yellow" (string|null) Color of buttons background
"separator": {
"enabled": true, // Enable separators?
"width": "60%" // Exaples: "100%", "80%", "60%" (string|null) Width of separators over images (relative to image width from the left)
},
"lists": {
"height": "800px", // Examples: "500px", "100%" (string|null) Maximum height of lists (`max-height` for animations working)
"background": null, // Examples: "#fafafa", "yellow" (string|null) Color of lists
"separator": null, // Examples: "#fafafa", "yellow" (string|null) Color of separators between rows
"separated": true, // Separate lists from its buttons?
"blocks": true, // Blocks instead of plain text?
"arrow": true // Add arrow at the right?
},
"texts": {
"position": {
"vertical": "center" // Values: "top", "center", "bottom" (string|null) Position of texts of ascendants categories
},
"width": "max(8rem, 20%)", // Examples: "60%", "5rem", "max(8rem, 20%)" (string|null) Width of the text section (left side of buttons)
"background": false, // Enable wrapping element for texts?
"title": {
"color": "#020202" // Examples: "#fafafa", "yellow" (string|null) Color of titles
},
"description": {
"color": "#121212" // Examples: "#fafafa", "yellow" (string|null) Color of descriptions
}
},
"images": {
"filter": "contrast(1.2)" // Example: "contrast(1.2)" (string|null) Filter for images
}
}
}
},
"cart": {
"enabled": true // Enable the cart button?
},
"css": {
"catalog-button-cart-background": "#40a7e3",
"product-button-cart-background": "#40a7e3"
"catalog-button-cart-added-background": "#90be36",
"product-button-cart-added-background": "#90be36"
},
"account": {
"enabled": false // Enable the account section? (works only when opened from telegram `inline-button`)
},
"menu": {
"enabled": true, // Enable the main menu?
"position": "fixed" // Values: "fixed" (fixed to the bottom as a solid line), "relative" (at the top as separated buttons) (stirng) Position of the main menu
},
"header": {
"enabled": true, // Enable the header?
"position": "fixed" // Values: "fixed" (fixed to the bottom), "relative" (at the top) (stirng) Position of the header
},
"acquirings": {
"robokassa": {
"enabled": true, // Enable the Robokassa acquiring?
"mode": "test" // Values: "work", "test" (string) Mode of the Robokassa acquiring
}
},
"input": {
"deliveries": {
"site": [], // Values: "sim", "name", "destination", "address", "commentary"
"chat": [
"sim",
"name",
"destination",
"address",
"commentary"
] // Values: "sim", "name", "destination", "address", "commentary"
}
},
"deliveries": {
"cdek": {
"enabled": true, // Enable CDEK delivery?
"label": "CDEK" // Name of the CDEK delivery
}
},
"chats": [
{
"id": null, // Example: -1002599391893 (int) (negative number) The telegram chat identifier
"orders": true // Send orders? (for moderators)
}
]
}
```
## Suspensions
System of suspensions of chat-robot and Web App<br>
<br>
*System of suspensions of chat-robot and Web App*<br><br>
Make sure you have a **suspension** collection (can be created automatically)<br>
You can copy a clean suspension document without comments from here: `/examples/arangodb/collections/suspension.json`
```json
@ -122,3 +356,13 @@ You can copy a clean suspension document without comments from here: `/examples/
}
}
```
## Used by
*List of projects created on the basis of [huesos](https://git.svoboda.works/mirzaev/huesos)*
- ARMING [@arming_bot](https://t.me/arming_bot)<br>
*Russian weapons tuning shop* ([repository](https://git.svoboda.works/mirzaev/arming))

View File

@ -1,7 +1,7 @@
{
"name": "mirzaev/arming_bot",
"name": "mirzaev/huesos",
"description": "Chat-robot for tuning weapons",
"homepage": "https://t.me/arming_bot",
"homepage": "https://t.me/huesos",
"type": "chat-robot",
"keywords": [
"telegram",
@ -20,9 +20,10 @@
"require": {
"php": "^8.4",
"ext-gd": "^8.4",
"ext-intl": "^8.4",
"triagens/arangodb": "^3.8",
"mirzaev/minimal": "^2.2",
"mirzaev/arangodb": "^1.3",
"mirzaev/minimal": "^3.4.0",
"mirzaev/arangodb": "^2",
"badfarm/zanzara": "^0.9.1",
"nyholm/psr7": "^1.8",
"react/filesystem": "^0.1.2",
@ -32,11 +33,12 @@
"avadim/fast-excel-reader": "^2.19",
"ttatpuot/cdek-sdk2.0": "^1.2",
"guzzlehttp/guzzle": "^7.9",
"php-http/guzzle7-adapter": "^1.0"
"php-http/guzzle7-adapter": "^1.0",
"react/async": "^4.3"
},
"autoload": {
"psr-4": {
"mirzaev\\arming_bot\\": "mirzaev/arming_bot/system/"
"mirzaev\\huesos\\": "mirzaev/huesos/system/"
}
},
"minimum-stability": "stable",

770
composer.lock generated Executable file → Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
{
"urn": "/account",
"name": {
"en": "Account",
"ru": "Аккаунт"
},
"identifier": "account",
"style": {
"order": 1
},
"class": "",
"icon": {
"style": {},
"class": "loading spinner animated"
},
"image": {
"storage": null
}
}

View File

@ -0,0 +1,18 @@
{
"urn": "/cart",
"name": {
"en": "Cart",
"ru": "Корзина"
},
"style": {
"order": 999
},
"class": "cart",
"icon": {
"style": {},
"class": "shopping cart"
},
"image": {
"storage": null
}
}

View File

@ -0,0 +1,18 @@
{
"urn": "/",
"name": {
"en": "Main page",
"ru": "Главная страница"
},
"style": {
"order": 0
},
"class": "",
"icon": {
"style": {},
"class": "house"
},
"image": {
"storage": null
}
}

View File

@ -1,16 +1,27 @@
{
"status": "active",
"project": {
"name": "PROJECT"
},
"language": "en",
"currency": "usd",
"company": {
"identifier": null,
"tax": null,
"name": null,
"offer": false,
"sim": null,
"mail": null
}
"status": "active",
"project": {
"name": "PROJECT"
},
"language": "en",
"currency": "usd",
"company": {
"identifier": null,
"tax": null,
"name": null,
"offer": false,
"sim": null,
"mail": null
},
"search": {
"enabled": true,
"position": "fixed"
},
"cart": {
"enabled": true
},
"css": {
"catalog-button-cart-background": "#40a7e3",
"product-button-cart-background": "#40a7e3"
}
}

View File

@ -1,37 +1,41 @@
server {
listen 443 ssl;
listen [::]:443 ssl ipv6only=on;
#
# This section is commented out to make it possible to run NGINX without errors
# to generate TLS/SSL certificate via CertBot (see README.md)
#
# server {
# listen 443 default_server ssl;
# listen [::]:443 ssl default_server;
server_name domain.zone;
# server_name domain.zone;
root /var/www/project/mirzaev/huesos/system/public;
# root /var/www/huesos/mirzaev/huesos/system/public;
index index.php;
# index index.php;
ssl_certificate /etc/letsencrypt/live/domain.zone/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/domain.zone/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# ssl_certificate /etc/letsencrypt/live/domain.zone/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/domain.zone/privkey.pem;
# include /etc/letsencrypt/options-ssl-nginx.conf;
# ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
try_files $uri $uri/ /index.php;
}
# location / {
# try_files $uri $uri/ /index.php?$query_string;
# }
location /api/cdek {
rewrite ^/api/cdek(.*)$ /$1 break;
index cdek.php;
}
# location /api/cdek {
# rewrite ^/api/cdek(.*)$ /$1 break;
# index cdek.php;
# }
location ~ /(?<type>categories|products) {
root /var/www/arming_bot/mirzaev/arming_bot/system/storage;
try_files $uri =404;
}
# location ~ /(?<type>categories|products) {
# root /var/www/huesos/mirzaev/huesos/system/storage;
# try_files $uri =404;
# }
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
}
}
# location ~ \.php$ {
# include snippets/fastcgi-php.conf;
# fastcgi_pass unix:/run/php/php8.4-fpm.sock;
# }
# }
server {
listen 80 default_server;

View File

@ -5,7 +5,7 @@ Wants=network.target
After=syslog.target network-online.target
[Service]
ExecStart=sudo -u www-data /usr/bin/php /var/www/project/mirzaev/huesos/system/public/robot.php
ExecStart=sudo -u www-data /usr/bin/php /var/www/huesos/mirzaev/huesos/system/public/robot.php
PIDFile=/var/run/php/huesos.pid
RemainAfterExit=no
RuntimeMaxSec=3600s

File diff suppressed because it is too large Load Diff

View File

@ -1,43 +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\reservation,
mirzaev\arming_bot\models\traits\document as document_trait,
mirzaev\arming_bot\models\interfaces\document as document_interface,
mirzaev\arming_bot\models\interfaces\collection as collection_interface,
mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\enumerations\currency;
// Framework for ArangoDB
use mirzaev\arangodb\collection,
mirzaev\arangodb\document;
// Library for ArangoDB
use ArangoDBClient\Document as _document;
// Built-in libraries
use exception;
/**
* Model of order
*
* @uses !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* @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 order extends core implements document_interface, collection_interface
{
use document_trait;
/**
* Name of the collection in ArangoDB
*/
final public const string COLLECTION = 'order';
}

File diff suppressed because it is too large Load Diff

View File

@ -1,188 +0,0 @@
<?php
declare(strict_types=1);
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\cart,
mirzaev\arming_bot\models\telegram;
// Framework for Telegram
use Zanzara\Zanzara,
Zanzara\Context,
Zanzara\Config;
// Framework for ArangoDB
use mirzaev\arangodb\document;
ini_set('error_reporting', E_ALL ^ E_DEPRECATED);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
// Версия робота
define('ROBOT_VERSION', '1.0.0');
// Путь до настроек
define('SETTINGS', __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'settings');
// Путь до хранилища
define('STORAGE', __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'storage');
// Файл в формате xlsx с примером excel-документа для импорта каталога
define('CATALOG_EXAMPLE', STORAGE . DIRECTORY_SEPARATOR . 'example.xlsx');
// Файл в формате xlsx для импорта каталога
define('CATALOG_IMPORT', STORAGE . DIRECTORY_SEPARATOR . 'import.xlsx');
/**
* Ключ чат-робота Telegram
* @deprecated
*/
define('KEY', require(SETTINGS . DIRECTORY_SEPARATOR . 'telegram.php'));
define('TELEGRAM_KEY', require(SETTINGS . DIRECTORY_SEPARATOR . 'telegram.php'));
// Initialize dependencies
require __DIR__ . DIRECTORY_SEPARATOR
. '..' . DIRECTORY_SEPARATOR
. '..' . DIRECTORY_SEPARATOR
. '..' . DIRECTORY_SEPARATOR
. '..' . DIRECTORY_SEPARATOR
. 'vendor' . DIRECTORY_SEPARATOR
. 'autoload.php';
// Инициализация ядра контроллеров MINIMAL
/* new controller(new core, false); */
// Инициализация ядра моделей MINIMAL
new model(true);
$config = new Config();
$config->setParseMode(Config::PARSE_MODE_MARKDOWN);
$config->useReactFileSystem(true);
$bot = new Zanzara(TELEGRAM_KEY, $config);
$bot->onUpdate(function (Context $ctx): void {
// Initializing the message
$message = $ctx->getMessage();
// Initializing the "web app" data
$app = $message?->getWebAppData();
if (!empty($app)) {
// Initialized the "web app" data
// Initializing request from "web app" data
$request = json_decode($app->getData(), false, 10);
if ($request->type === 'cart_share') {
// Cart attaching
// Attaching cart to the Telegram account
telegram::cart_attach($ctx, $request->hash);
}
} else {
// Not initialized the "web app" data
// Initializing account
$account = $ctx->get('account');
if ($account) {
// Initialized the account
if (!empty($message)) {
// Initialized the message
// Initializing the contact data
$contact = $message?->getContact();
if (!empty($contact)) {
// Initialized the contact data
// Sanitizing received SIM-number (only numbers)
$sanitized = preg_replace('/[^\d]/', '', $contact->getPhoneNumber());
if (!empty($sanitized)) {
// Sanitized receiver SIM-number
// Writing receiver SIM-number into the account
$account->receiver = ['sim' => (int) $sanitized] + ($account->receiver ?? []);
// Deabstracting the language parameter
$account->language = $account->language->name;
if (document::update($account->__document())) {
// Writed the account instance into the ArangoDB document
$ctx->sendMessage(
<<<TXT
*SIM\-номер зарегистрирован:* $sanitized
TXT,
[
'reply_markup' => [
'remove_keyboard' => true
]
]
)->then(function ($message) use ($ctx) {
// Sended message
// Sending the account parameters menu
telegram::account_parameters($ctx);
});
} else {
// Not writed the account instance into the ArangoDB document
// Sending the message
$ctx->sendMessage('⚠️ *Не удалось записать SIM\-номер*');
}
}
}
}
}
}
unset($app);
});
$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) => 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) => telegram::_mail($ctx));
$bot->onCbQueryData(['import_request'], fn($ctx) => telegram::import_request($ctx));
$bot->onCbQueryData(['order'], fn($ctx) => telegram::order($ctx));
$bot->onCbQueryData(['tuning'], fn($ctx) => telegram::tuning($ctx));
$bot->onCbQueryData(['brands'], fn($ctx) => telegram::brands($ctx));
$bot->onCbQueryData(['receiver_sim_choose'], fn($ctx) => telegram::receiver_sim_choose($ctx));
$bot->onCbQueryData(['receiver_sim_request'], fn($ctx) => telegram::receiver_sim_request($ctx));
$bot->onCbQueryData(['receiver_sim_input'], fn($ctx) => telegram::receiver_sim_input($ctx));
$bot->onCbQueryData(['receiver_sim_write'], fn($ctx) => telegram::receiver_sim_write($ctx));
$bot->onCbQueryData(['receiver_name_choose'], fn($ctx) => telegram::receiver_name_choose($ctx));
$bot->onCbQueryData(['receiver_name_request'], fn($ctx) => telegram::receiver_name_request($ctx));
$bot->onCbQueryData(['receiver_name_input'], fn($ctx) => telegram::receiver_name_input($ctx));
$bot->onCbQueryData(['receiver_name_write'], fn($ctx) => telegram::receiver_name_write($ctx));
$bot->onException(function (Context $ctx, $exception) {
var_dump($exception);
});
// Инициализация middleware с обработкой аккаунта
$bot->middleware([telegram::class, "account"]);
// Инициализация middleware с обработкой технических работ разных уровней
$bot->middleware([telegram::class, "suspension"]);
// Запуск чат-робота
$bot->run();

View File

@ -1,11 +0,0 @@
@charset "UTF-8";
section#account {
z-index: 999;
position: fixed;
bottom: 20px;
left: 20px;
height: 16px;
display: flex;
}

View File

@ -1,20 +0,0 @@
@charset "UTF-8";
@keyframes slide-down-revert {
0% {
transform: translate(0, 0%);
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
}
100% {
transform: translate(0, -100%);
clip-path: polygon(0% 100%, 100% 100%, 100% 200%, 0% 200%);
}
}
.slide.down.revert.animated {
animation-duration: var(--animation-duration, 0.2s);
animation-name: slide-down-revert;
animation-fill-mode: forwards;
animation-timing-function: cubic-bezier(1, 0, 1, 1);
}

View File

@ -1,188 +0,0 @@
@charset "UTF-8";
main>section#categories {
width: var(--width);
display: flex;
flex-flow: row wrap;
gap: var(--gap, 5px);
}
main>section#categories>a.category[type="button"] {
--padding: 0.7rem;
position: relative;
height: 23px;
padding: var(--padding);
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#categories:last-child {
/* margin-bottom: unset; */
}
main>section#categories>a.category[type="button"]:has(>img) {
min-width: calc(50% - var(--padding) * 2);
height: 180px;
padding: unset;
}
main>section#categories>a.category[type="button"]>img {
position: absolute;
left: -5%;
top: -5%;
width: 110%;
height: 110%;
object-fit: cover;
/* filter: blur(1px); */
filter: brightness(60%);
}
main>section#categories>a.category[type="button"]:is(:hover, :focus)>img {
filter: unset;
}
main>section#categories>a.category[type="button"]:has(>img)>p {
position: absolute;
left: var(--padding);
bottom: var(--padding);
right: var(--padding);
margin: unset;
width: min-content;
border-radius: 0.75rem;
background: var(--tg-theme-section-bg-color);
box-shadow: 0px 0px 8px 4px rgba(0, 0, 0, 0.3);
-webkit-box-shadow: 0px 0px 8px 4px rgba(0, 0, 0, 0.3);
-moz-box-shadow: 0px 0px 8px 4px rgba(0, 0, 0, 0.3);
}
main>section#categories>a.category[type="button"]>p {
z-index: 100;
padding: 8px 16px;
}
main>section#filters {
width: var(--width);
max-height: 2.5rem;
display: flex;
align-items: start;
}
main>section#products {
--column: calc((100% - var(--gap)) / 2);
width: var(--width);
display: grid;
grid-gap: var(--gap);
grid-template-columns: repeat(2, var(--column));
grid-auto-flow: row dense;
}
main>section#products>div.column {
width: 100%;
display: flex;
flex-direction: column;
gap: var(--gap);
}
main>section#products>div.column>article.product {
position: relative;
width: 100%;
display: flex;
flex-grow: 0;
flex-direction: column;
border-radius: 0.75rem;
overflow: clip;
cursor: pointer;
backdrop-filter: brightness(0.7);
background-color: var(--tg-theme-section-bg-color);
}
main>section#products>div.column>article.product:is(:hover, :focus) {
/* flex-grow: 0.1; */
/* background-color: var(--tg-theme-section-bg-color); */
}
main>section#products>div.column>article.product:is(:hover, :focus)>* {
transition: 0s;
}
main>section#products>div.column>article.product:not(:is(:hover, :focus))>* {
transition: 0.2s ease-out;
}
main>section#products>div.column>article.product>a>img:first-of-type {
width: 100%;
image-rendering: auto;
border-radius: 0.75rem;
box-shadow: 0px 5px 70px 20px rgba(0, 0, 0, 0.3);
-webkit-box-shadow: 0px 5px 70px 20px rgba(0, 0, 0, 0.3);
-moz-box-shadow: 0px 5px 70px 20px rgba(0, 0, 0, 0.3);
}
main>section#products>div.column>article.product>a>img:first-of-type+* {
margin-top: auto;
}
main>section#products>div.column>article.product>a>p.title {
z-index: 50;
margin: unset;
padding: 4px 8px 8px;
font-size: 0.9rem;
font-weight: bold;
overflow-wrap: anywhere;
hyphens: auto;
}
main>section#products>div.column>article.product>a>p.title>span {
color: var(--tg-theme-hint-color);
}
main>section#products>div.column>article.product>div[data-product="buttons"]:last-of-type {
z-index: 100;
height: 33px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
border-radius: 0.75rem;
}
main>section#products>div.column>article.product[data-product-amount]:not(:is([data-product-amount="0"], [data-product-amount="1"]))>div[data-product="buttons"]:last-of-type {
container-type: inline-size;
container-name: product-buttons;
}
main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"] {
padding: 0;
flex-grow: 1;
}
main>section#products>div.column>article.product:is([data-product-amount="0"], [data-product-amount="1"])>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-parameter="amount"],
main>section#products>div.column>article.product[data-product-amount="0"]>div[data-product="buttons"]>button:is([data-product-button="write"], [data-product-button="delete"]) {
display: none;
}
main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-parameter="amount"]:after {
content: '*';
margin: 0 0.2rem;
}
main>section#products>div.column>article.product[data-product-amount]:not([data-product-amount="0"])>div[data-product="buttons"] {
filter: hue-rotate(calc(120deg + var(--hue-rotate-offset, 0deg)));
}
@container product-buttons (max-width: 200px) {
main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span:is([data-product-parameter="cost"], [data-product-parameter="currency"]) {
display: none;
}
main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-parameter="amount"]:after {
content: unset;
}
}

View File

@ -1,126 +0,0 @@
@charset "UTF-8";
section[data-type="select"] {
--width: max(14rem, 20vw);
--height-element: 2rem;
--height-close: var(--height-element);
--height-open: max-content;
position: relative;
width: var(--width);
height: var(--height-close);
display: flex;
flex-direction: column;
cursor: pointer;
border-radius: 0.75rem;
overflow-x: hidden;
background-color: var(--tg-theme-button-color);
transition: 0s;
}
section[data-type="select"]>button:has(>i.icon.close) {
align-self: end;
height: 100%;
padding: 0 0.4rem;
}
section[data-type="select"]:has(input[id$="title"]:checked)>button:has(>i.icon.close),
section[data-type="select"]:focus>button:has(>i.icon.close) {
display: none;
}
section[data-type="select"]:not(:focus, :has(>button>i.icon.close)):after {
z-index: 30;
content: '';
top: calc(50% - 2.5px);
right: 1rem;
position: absolute;
width: 0;
height: 0;
pointer-events: none;
border-left: 5px solid transparent;
border-top: 5px solid var(--tg-theme-button-text-color, black);
border-right: 5px solid transparent;
}
section[data-type="select"]>input {
left: -99999px;
position: absolute;
opacity: 0;
}
section[data-type="select"]>label {
z-index: 10;
order: 2;
top: 0;
position: absolute;
width: 100%;
height: var(--height-element);
padding: 0 1rem;
display: none;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
box-sizing: border-box;
pointer-events: none;
color: var(--tg-theme-button-text-color);
transition: 0s;
}
section[data-type="select"]:is([data-select="open"], :focus)>input:not(:checked)+label[for$='title'] {
display: none;
}
section[data-type="select"]:is([data-select="open"], :focus)>input:not(:checked)+label[for$='all']:not(:hover, :active, :focus) {
filter: brightness(90%);
}
section[data-type="select"]>input:not(:checked)+label {
cursor: pointer;
filter: brightness(80%);
}
section[data-type="select"]:is([data-select="open"], :focus)>input+label:hover {
filter: brightness(110%);
}
section[data-type="select"]:is([data-select="open"], :focus)>input:checked+label:hover {
filter: brightness(120%);
}
section[data-type="select"]:is([data-select="open"], :focus)>input+label:is(:active, :focus) {
filter: brightness(60%);
}
section[data-type="select"]:is([data-select="open"], :focus)>input:checked+label:is(:active, :focus) {
filter: brightness(70%);
}
section[data-type="select"]>input:checked+label {
order: 1;
max-width: calc(var(--width) - 2rem - 10px);
display: inline;
line-height: var(--height-element);
padding-right: 0;
}
section[data-type="select"]:is([data-select="open"], :focus)>input:checked+label {
max-width: initial;
padding-right: initial;
}
section[data-type="select"]:is([data-select="open"], :focus) {
height: var(--height-open, max-content);
}
section[data-type="select"]:is([data-select="open"], :focus)>label {
position: relative;
display: inline;
line-height: var(--height-element);
pointer-events: all;
}
@media only screen and (max-width: 500px) {
section[data-type="select"]:only-child {
--width: 100%
}
}

View File

@ -1,54 +0,0 @@
@charset "UTF-8";
header>nav#menu {
container-type: inline-size;
container-name: menu;
margin-bottom: 1rem;
width: var(--width);
min-height: 3rem;
display: flex;
flex-flow: row wrap;
gap: 1rem;
border-radius: 1.375rem;
overflow: hidden;
}
header>nav#menu>a[type="button"] {
height: 3rem;
padding: unset;
border-radius: 1.375rem;
color: var(--tg-theme-button-text-color);
background-color: var(--tg-theme-button-color);
}
header>nav#menu>a[type=button]>:first-child {
margin-left: 1rem;
}
header>nav#menu>a[type="button"]>* {
margin-right: 1rem;
}
@container header (max-width: 450px) {
header>nav#menu>a[type="button"]:nth-child(1)>i.icon+span {
display: none;
}
}
@container header (max-width: 350px) {
header>nav#menu>a[type="button"]:nth-child(2)>i.icon+span {
display: none;
}
}
@container header (max-width: 250px) {
header>nav#menu>a[type="button"]:nth-child(3)>i.icon+span {
display: none;
}
}
@container header (max-width: 150px) {
header>nav#menu>a[type="button"]>i.icon+span {
display: none;
}
}

View File

@ -1,6 +0,0 @@
<?php
return [
'account' => '',
'secret' => ''
];

View File

@ -1,6 +0,0 @@
<?php
return [
'account' => '',
'secret' => ''
];

View File

@ -1,16 +0,0 @@
{% if categories is not empty %}
<section id="categories" class="unselectable">
{% for category in categories %}
<a id="category_{{ category.identifier }}" data-category-identifier="{{ category.identifier }}" class="category"
type="button" href="?category={{ category.identifier }}"
onclick="core.catalog.parameters.set('category', {{ category.identifier }}); return !core.catalog.search(event);"
onkeydown="event.keyCode === 13 && (core.catalog.parameters.set('category', {{ category.identifier }}), core.catalog.search(event))"
tabindex="3">
{% if category.images %}
<img src="{{ category.images.0.storage.400 }}" alt="{{ category.name }}" ondragstart="return false;">
{% endif %}
<p>{{ category.name }}</p>
</a>
{% endfor %}
</section>
{% endif %}

View File

@ -1,7 +0,0 @@
<search id="search">
<label class="unselectable"><i class="icon search"></i></label>
{% set buffer_search = session.buffer.catalog.search.text ?? account.buffer.catalog.search.text %}
<input placeholder="{{ language.name == 'ru' ? 'Поиск по каталогу' : 'Search in the catalog' }}" type="search" tabindex="1"
onkeyup="event.keyCode === 9 || core.catalog.parameters.set('text', this.value); return core.catalog.search(event, this)"
{% if buffer_search is not empty %} value="{{ buffer_search }}" {% endif %} />
</search>

View File

@ -1,28 +0,0 @@
{% block title %}
<title>{% if head.title is not empty %}{{ head.title }}{% else %}{{ settings.project.name }}{% endif %}</title>
{% endblock %}
{% block meta %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% for meta in head.metas %}
<meta {% for name, value in meta.attributes %}{{ name }}="{{ value }}" {% endfor %}>
{% endfor %}
{% endblock %}
{% block css %}
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/main.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/loading_spinner.css" rel="preload" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/loading.css" rel="preload" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/window.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/fonts/dejavu.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/fonts/kabrio.css" rel="preload" />
<style>
:root {
--currency: "{{ currency.symbol ?? '$' }}";
--days: "{{ language.name == 'ru' ? 'дней' : 'days' }}";
--days-short: "{{ language.name == 'ru' ? 'дн' : 'd' }}";
}
</style>
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/animations.css" />
{% endblock %}

View File

@ -1,15 +0,0 @@
{% use "/themes/default/menu.html" with css as menu_css, body as menu_body, js as menu_js %}
{% block css %}
{{ block('menu_css') }}
{% endblock %}
{% block body %}
<header>
{{ block('menu_body') }}
</header>
{% endblock %}
{% block js %}
{{ block('menu_js') }}
{% endblock %}

View File

@ -1,32 +0,0 @@
{% block css %}
<link type="text/css" rel="stylesheet" href="/themes/default/css/menu.css">
{% for button in menu %}
{% if button.icon %}
<link type="text/css" rel="stylesheet" href="/themes/default/css/icons/{{ button.icon.class|replace({' ': '_'}) }}.css">
{% endif %}
{% endfor %}
{% endblock %}
{% block body %}
<nav id="menu">
{% for button in menu %}
<a href='{{ button.urn }}' onclick="return core.loader.load('{{ button.urn }}');" type="button" class="unselectable"
title="{{ button.name }}" {% if button.style %}
style="{% for parameter, value in button.style %}{{ parameter ~ ': ' ~ value ~ '; ' }}{% endfor %}" {% endif %}>
{% if button.icon %}
<i class="icon {{ button.icon.class }}" {% if button.icon.style %}
style="{% for parameter, value in button.icon.style %}{{ parameter ~ ': ' ~ value ~ '; ' }}{% endfor %}" {% endif
%}></i>
{% endif %}
<span>{{ button.name }}</span>
{% if button.image.storage %}
<img src="{{ button.image.storage }}" alt="{{ button.name }}" ondragstart="return false;">
{% endif %}
</a>
{% endfor %}
</nav>
{% endblock %}
{% block js %}
<script src="/js/menu.js"></script>
{% endblock %}

View File

@ -2,10 +2,10 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\controllers;
namespace mirzaev\huesos\controllers;
// Files of the project
use mirzaev\arming_bot\controllers\core;
use mirzaev\huesos\controllers\core;
// Framework for PHP
use mirzaev\minimal\http\enumerations\status;
@ -13,7 +13,7 @@ use mirzaev\minimal\http\enumerations\status;
/**
* Controller of account
*
* @package mirzaev\arming_bot\controllers
* @package mirzaev\huesos\controllers
*
* @param array $errors Registry of errors
*

View File

@ -0,0 +1,375 @@
<?php
declare(strict_types=1);
namespace mirzaev\huesos\controllers\api\acquirings;
// Files of the project
use mirzaev\huesos\controllers\core,
mirzaev\huesos\models\order,
mirzaev\huesos\models\cart,
mirzaev\huesos\models\product,
mirzaev\huesos\models\telegram,
mirzaev\huesos\models\acquirings\robokassa as model,
mirzaev\huesos\models\enumerations\currency,
mirzaev\huesos\models\enumerations\language;
// Framework for PHP
use mirzaev\minimal\http\enumerations\content,
mirzaev\minimal\http\enumerations\status;
// Framework for ArangoDB
use mirzaev\arangodb\collection,
mirzaev\arangodb\document;
// Framework for Telegram
use Zanzara\Zanzara as zanzara,
Zanzara\Context as context,
Zanzara\Config as config,
Zanzara\Telegram\Type\Message as message;
// Event manager for PHP
use React\EventLoop\Loop as loop;
// Built-in libraries
use DateTime as datetime,
Exception as exception;
/**
* Controller of robokassa
*
* @package mirzaev\huesos\controllers\api\acquirings
*
* @param array $errors Registry of errors
*
* @method null result()
* @method null success()
* @method null fail()
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class robokassa extends core
{
/**
* Errors
*
* @var array $errors Registry of errors
*/
protected array $errors = [
'session' => [],
'account' => [],
'order' => [],
'robokassa' => []
];
/**
* Result
*
* @param null
*/
public function result(
?string $OutSum = null,
?string $InvId = null,
?string $SignatureValue = null,
?string $PaymentMethod = null,
?string $IncSum = null,
?string $IncCurrLabel = null,
?string $IsTest = null,
?string $EMail = null,
?string $Fee = null,
string ...$other
): null {
// Searching for the order
$order = order::_read(
filter: 'd._key == @_key && d.paid != true',
sort: 'd.created DESC, d._key DESC',
amount: 1,
parameters: ['_key' => $InvId],
errors: $this->errors['robokassa']
);
if ($order instanceof order) {
// Initialized the order
if (model::result(identifier: (int) $InvId, cost: $OutSum, hash: $SignatureValue, test: (bool) $IsTest, errors: $this->errors['robokassa'])) {
// Verified the payment
// Initializing the cart
$cart = $order->cart();
if ($cart instanceof cart) {
// Initialized the cart
if ((float) ($cart->cost() + ($cart->buffer['delivery']['cost'] ?? 0)) === (float) $OutSum) {
// Full payment received
// Writing that the order being been paid
$order->paid = true;
/* $order->term = null; */
if (document::update($order->__document(), errors: $this->errors['order'])) {
// Writed into ArangoDB
// Initializing identifier of the order
$identifier = $order->__document()->getKey();
// Initializing the customer account
$customer = $order->account();
$config = new config();
$config->setParseMode(config::PARSE_MODE_MARKDOWN);
$config->useReactFileSystem(true);
/* $config->setLoop(loop::get()); */
$robot = new zanzara(TELEGRAM_KEY, $config);
// Sending the message
$robot->getTelegram()->sendMessage("✅ *Заказ \#$identifier оплачен*", ['chat_id' => $customer->identifier])
->then(function ($message) use ($robot, $cart, $order, $identifier, $customer) {
// Sended the message
// Initializing the chats for sending registry
$chats = [];
foreach ($this->settings->chats as $chat) {
// Iteration over chats
if ($chat['orders']) {
// Authorized to receive orders
// Writing into the chats for sending registry
$chats[] = $chat['id'];
}
}
if (count($chats) > 0) {
// Initialized chats
// Declaring the formatted list of products for message
$list = '';
// Declaring total cost of products
$cost = $cart->cost($list);
// Escaping the formatted list of products for the message
$list = telegram::unmarkdown($list);
// Initializing currency symbol
$symbol = ($account->currency ?? currency::rub)->symbol();
// Declaring delivery texts
$delivery_cost = $delivery_days = $delivery_address = '';
if (!empty($cart->buffer['delivery'])) {
// Initialized delibery data
// Initializing delivery cost for message
$delivery_cost = telegram::unmarkdown((string) $cart->buffer['delivery']['cost']);
// Initializing delivery days for message
$delivery_days = telegram::unmarkdown((string) $cart->buffer['delivery']['days']);
// Initializing delivery address for message
$delivery_address = telegram::unmarkdown($cart->buffer['delivery']['location']['name'] . ', ' . $cart->buffer['delivery']['street']);
}
// Initializing receiver domain for the message
$domain = telegram::unmarkdown($customer->domain);
// Initializing receiver SIM for the message
$sim = telegram::unmarkdown((string) $customer->receiver['sim']);
// Initializing receiver name for the message
$name = telegram::unmarkdown($customer->receiver['name']);
// Initializing receiver address for the message
$address = telegram::unmarkdown($customer->receiver['address']);
// Initializing the message cost part
$part_cost = "*Стоимость:* $cost$symbol";
if (!empty($delivery_cost)) $part_cost .= " \+ $delivery_cost$symbol";
if (!empty($delivery_days)) $part_cost .= " \($delivery_days дней\)";
// Initializing the message delivery part
$part_delivery = '';
if (!empty($delivery_address)) $part_delivery .= "*Адрес доставки:* $delivery_address";
if (!empty($part_delivery)) $part_delivery = "\n$part_delivery";
// Initializing the message receiver part
$part_receiver = "*Получатель:* @$domain";
if (!empty($sim)) $part_receiver .= " $sim";
if (!empty($name)) $part_receiver .= "\n*$name*";
if (!empty($address)) $part_receiver .= "\n\n*Адрес:* $address";
// Initializing the message commentary part
$part_commentary = '';
if (!empty($order->commentary)) $part_commentary .= "\n\n*Комментарий:* " . $order->commentary;
// Sending messages
$robot->getTelegram()->sendBulkMessage(
$chats,
<<<TXT
📦 *Заказ* \#$identifier
$list
$part_cost$part_delivery
$part_receiver$part_commentary
TXT,
[
'reply_markup' => [
'inline_keyboard' => [
[
['text' => '✉️ Чат с покупателем', 'url' => 'https://t.me/' . $customer->domain]
]
],
'disable_notification' => true
]
]
);
}
});
// Sending response
$this->response
->start()
->clean()
->sse()
->write("OK$identifier")
->validate($this->request)
?->body()
->end();
} else throw new exception('Failed to update the order');
}
}
}
}
// Exit (success/fail)
return null;
}
/**
* Success
*
* @return null
*/
public function success(
?string $OutSum = null,
?string $InvId = null,
?string $SignatureValue = null,
?string $IsTest = null,
?string $Culture = null,
string ...$other
): null {
// Searching for the order
$order = order::_read(
filter: 'd._key == @_key',
sort: 'd.created DESC, d._key DESC',
amount: 1,
parameters: ['_key' => $InvId],
errors: $this->errors['robokassa']
);
if ($order instanceof order) {
// Initialized the order
if (model::success(identifier: (int) $InvId, cost: $OutSum, hash: $SignatureValue, test: (bool) $IsTest, errors: $this->errors['robokassa'])) {
// Verified the payment
if ($IsTest == 1) {
// Enabled the test mode
// Initializing a paragraph abount the test mode
$this->view->test = $this->language === language::ru ? 'Тестовый режим' : 'Test mode';
}
// Render page
$page = $this->view->render('api/acquirings/robokassa/success.html', [
'h2' => $this->language === language::ru ? 'Заказ #' . $order->getKey() . ' оплачен' : 'Order #' . $order->getKey() . ' paid',
'identifier' => $order->getKey(),
'closing' => [
'title' => $this->language === language::ru ? 'Закрытие окна' : 'Window closing',
'iterator' => 30
]
]);
// Sending response
$this->response
->start()
->clean()
->sse()
->write($page)
->validate($this->request)
?->body()
->end();
// Deinitializing rendered page
unset($page);
}
}
// Exit (success/fail)
return null;
}
/**
* Fail
*
* @return null
*/
public function fail(
?string $OutSum = null,
?string $InvId = null,
?string $IsTest = null,
?string $Culture = null,
string ...$other
): null {
// Searching for the order
$order = order::_read(
filter: 'd._key == @_key',
sort: 'd.created DESC, d._key DESC',
amount: 1,
parameters: ['_key' => $InvId],
errors: $this->errors['robokassa']
);
if ($order instanceof order) {
// Initialized the order
if ($IsTest == 1) {
// Enabled the test mode
// Initializing a paragraph abount the test mode
$this->view->test = $this->language === language::ru ? 'Тестовый режим' : 'Test mode';
}
// Render page
$page = $this->view->render('api/acquirings/robokassa/fail.html', [
'h2' => $this->language === language::ru ? 'Заказ #' . $order->getKey() . ' не оплачен' : 'Order #' . $order->getKey() . ' not paid',
'identifier' => $order->getKey(),
'closing' => [
'title' => $this->language === language::ru ? 'Закрытие окна' : 'Window closing',
'iterator' => 30
]
]);
// Sending response
$this->response
->start()
->clean()
->sse()
->write($page)
->validate($this->request)
?->body()
->end();
// Deinitializing rendered page
unset($page);
}
// Exit (success/fail)
return null;
}
}

View File

@ -2,23 +2,34 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\controllers;
namespace mirzaev\huesos\controllers;
// Files of the project
use mirzaev\arming_bot\controllers\core,
mirzaev\arming_bot\models\cart as model,
mirzaev\arming_bot\models\product,
mirzaev\arming_bot\models\menu,
mirzaev\arming_bot\models\enumerations\language;
use mirzaev\huesos\controllers\core,
mirzaev\huesos\models\cart as model,
mirzaev\huesos\models\product,
mirzaev\huesos\models\menu,
mirzaev\huesos\models\telegram,
mirzaev\huesos\models\enumerations\currency,
mirzaev\huesos\models\enumerations\language;
// Framework for PHP
use mirzaev\minimal\http\enumerations\content,
mirzaev\minimal\http\enumerations\status;
// Framework for Telegram
use Zanzara\Zanzara as zanzara,
Zanzara\Context as context,
Zanzara\Config as config,
Zanzara\Telegram\Type\Message as message;
// Event manager for PHP
use React\EventLoop\Loop as loop;
/**
* Controller of cart
*
* @package mirzaev\arming_bot\controllers
* @package mirzaev\huesos\controllers
*
* @param model|null $cart Instance of the cart
* @param array $errors Registry of errors
@ -74,6 +85,9 @@ final class cart extends core
parameters: ['language' => $this->language->name],
errors: $this->errors['menu']
);
// Universalizing
if ($this->view->menu instanceof _document) $this->view->menu = [$this->view->menu];
}
// Initializing the cart
@ -110,12 +124,26 @@ final class cart extends core
$this->view->formatted = $formatted;
}
// Initializing types of avaiabld deliveries
$this->view->deliveries = [
'cdek' => [
'label' => 'CDEK'
]
];
if (array_search('address', $this->settings->input['deliveries']['site'], true) !== false) {
// The deliveries input is enabled for the site
// Declaring the deliveries list
$deliveries = [];
foreach ($this->settings->deliveries as $key => $value) {
// Iterating over deliveries
if ($value['enabled']) {
// The delivery is enabled
// Writing into the deliveries list
$deliveries[$key] = ['label' => $value['label']];
}
}
// Writing the deliveries list into the templater variables
$this->view->deliveries = $deliveries;
}
if (str_contains($this->request->headers['accept'], content::json->value)) {
// Request for JSON response
@ -441,85 +469,147 @@ final class cart extends core
}
/**
* Pay
* Attach
*
* Pay for the cart
* Attach the cart
*
* @return null
*
* @todo кажется я сделал хуйню
*/
public function pay(): null
public function attach(?string $share = null): null
{
if (str_contains($this->request->headers['accept'], content::json->value)) {
// Request for JSON response
// Initializing the cart
$this->cart ??= $this->account?->cart() ?? $this->session?->cart();
if ($this->account) {
// Initialized the account
if ($this->cart instanceof model) {
// Initialized the cart
$config = new config();
$config->setParseMode(config::PARSE_MODE_MARKDOWN);
$config->useReactFileSystem(true);
/* $config->setLoop(loop::get()); */
if ($share = $this->cart->share ?? $this->cart->share()) {
// The cart is available for sharing
$robot = new zanzara(TELEGRAM_KEY, $config);
// Sending response
$this->response
->start()
->clean()
->sse()
->json([
'share' => $share,
'errors' => $this->errors
])
->validate($this->request)
?->body()
->end();
}
// Initializing cart
$cart = model::_read(
filter: 'd.share == @share',
sort: 'd.updated DESC, d.created DESC, d._key DESC',
amount: 1,
page: 1,
parameters: ['share' => $share]
);
// Deinitializing unnecessary variables
unset($hash);
unset($share);
if ($cart instanceof model) {
// Initialized the cart
// Unsharing the cart
$cart->unshare();
// Connecting the cart to the account
$edge = $this->account->connect($cart);
if (!empty($edge)) {
// Connected the cart to the account
// Initializing products in the cart
$products = $cart->products(language: $this->account->language ?? language::ru, currency: $this->account->currency ?? currency::rub);
if (!empty($products)) {
// Initialized products in the cart
// Declaring the formatted list of products for message
$list = '';
// Declaring total cost of products
$cost = $cart->cost($list);
// Escaping the formatted list of products for the message
$list = telegram::unmarkdown($list);
// Deinitializing unnecessary variables
unset($products, $product, $row);
// Initializing identifier of the cart
$identifier = $cart->getKey();
// Initializing currency symbol
$symbol = ($this->account->currency ?? currency::rub)->symbol();
// Declaring delivery texts
$delivery_cost = $delivery_days = $delivery_address = '';
if (!empty($cart->buffer['delivery'])) {
// Initialized delibery data
// Initializing delivery cost for message
$delivery_cost = telegram::unmarkdown((string) $cart->buffer['delivery']['cost']);
// Initializing delivery days for message
$delivery_days = telegram::unmarkdown((string) $cart->buffer['delivery']['days']);
// Initializing delivery address for message
$delivery_address = telegram::unmarkdown($cart->buffer['delivery']['location']['name'] . ', ' . $cart->buffer['delivery']['street']);
}
// Initializing the message cost part
$part_cost = "*Стоимость:* $cost$symbol";
if (!empty($delivery_cost)) $part_cost .= " \+ $delivery_cost$symbol";
if (!empty($delivery_days)) $part_cost .= " \($delivery_days дней\)";
// Initializing the message delivery part
$part_delivery = '';
if (!empty($delivery_address)) $part_cost .= "*Адрес доставки:* $delivery_address";
if (!empty($part_delivery)) $part_delivery = "\n$part_delivery";
$robot->getTelegram()->sendMessage(
<<<TXT
🛒 *Добавлена корзина* \#$identifier
$list
$part_cost$part_delivery
TXT,
[
'chat_id' => $this->account->identifier,
'reply_markup' => [
'inline_keyboard' => [
[
['text' => '🚚 Оформить доставку', 'callback_data' => 'cart_delivery'],
],
],
'disable_notification' => true
]
]
);
}
}
// Deinitializing unnecessary variables
unset($cart, $list);
}
}
}
// Exit (success/fail)
return null;
}
// Deinitializing unnecessary variables
unset($cart);
/**
* Robokassa
*
* HTML-document with robokassa iframe
*
* @param null
*
* @todo THIS MUST BE A PAYMENT OF ORDER IN THE FUTURE, NOT CART
*/
public function robokassa(): null
{
// Initializing the cart
$this->cart ??= $this->account?->cart() ?? $this->session?->cart();
// Initializing the cart data
$this->view->cart = $this->cart;
$this->view->summary = $this->cart?->summary(currency: $this->currency);
if (str_contains($this->request->headers['accept'], content::any->value)) {
// Request for any response
// Render page
$page = $this->view->render('iframes/robokassa.html');
$robot->getLoop()->run();
// Sending response
$this->response
->start()
->clean()
->sse()
->write($page)
->json([
'success' => true,
'errors' => $this->errors
])
->validate($this->request)
?->body()
->end();
// Deinitializing rendered page
unset($page);
}
// Exit (success/fail)

View File

@ -2,16 +2,17 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\controllers;
namespace mirzaev\huesos\controllers;
// Files of the project
use mirzaev\arming_bot\controllers\core,
mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\entry,
mirzaev\arming_bot\models\category,
mirzaev\arming_bot\models\product,
mirzaev\arming_bot\models\cart,
mirzaev\arming_bot\models\menu;
use mirzaev\huesos\controllers\core,
mirzaev\huesos\models\enumerations\language,
mirzaev\huesos\models\catalog as model,
mirzaev\huesos\models\entry,
mirzaev\huesos\models\category,
mirzaev\huesos\models\product,
mirzaev\huesos\models\cart,
mirzaev\huesos\models\menu;
// Framework for PHP
use mirzaev\minimal\http\enumerations\content,
@ -24,7 +25,7 @@ use ArangoDBClient\Document as _document;
/**
* Controller of catalog
*
* @package mirzaev\arming_bot\controllers
* @package mirzaev\huesos\controllers
*
* @param cart|null $cart Instance of the cart
* @param array $errors Registry of errors
@ -52,7 +53,8 @@ final class catalog extends core
'session' => [],
'account' => [],
'menu' => [],
'catalog' => []
'catalog' => [],
'cart' => []
];
/**
@ -80,9 +82,13 @@ final class catalog extends core
// Initializing the cart
$this->cart ??= $this->account?->cart() ?? $this->session?->cart();
// Initializing summary data of the cart
$summary = $this->cart?->summary(currency: $this->currency, errors: $this->errors['cart']);
// Initializing the cart data
$this->view->cart = [
'products' => $this->cart?->products(language: $this->language, currency: $this->currency)
'products' => $this->cart?->products(language: $this->language, currency: $this->currency),
'summary' => $summary
];
// Validating received product identifier
@ -113,6 +119,7 @@ final class catalog extends core
'cart' => [
'amount' => $this->view->cart['products'][$this->view->product->getId()]['amount'] ?? 0,
'text' => [
'cart' => $this->language === language::ru ? 'Корзина' : 'Cart',
'add' => $this->language === language::ru ? 'Добавить в корзину' : 'Add to the cart',
'added' => $this->language === language::ru ? 'Добавлено в корзину' : 'Added to the cart'
]
@ -189,31 +196,26 @@ final class catalog extends core
// Write to the response buffer
$response['category'] = ['name' => $category->name ?? null];
// Search for categories that are descendants of $category
$entries = entry::search(
// Searching for entries that are descendants of $category
$search = model::search(
document: $category,
amount: 50,
amount: 200,
depth: 100,
categories_merge: 'name: v.name.@language',
/* products_merge: 'DISTINCT MERGE(d, {name: d.name.@language, description: d.description.@language, compatibility: d.compatibility.@language, brand: d.brand.@language, cost: d.cost.@currency})', */
parameters: ['language' => $this->language->name],
errors: $this->errors['catalog']
);
// Initialize buffers of entries (in singular, by parameter from ArangoDB)
$category = $product = [];
if (isset($this->settings->catalog['categories']['structure']) && $this->settings->catalog['categories']['structure'] === 'pages') {
// Pages
foreach ($entries as $entry) {
// Iterate over entries (descendands)
// Write entry to the buffer of entries (sort by $category and $product)
${$entry->_type}[] = $entry;
// Write to the buffer of global variables of view templater
$this->view->categories = $search['categories'];
}
// Write to the buffer of global variables of view templater
$this->view->categories = $category;
// Write to the buffer of global variables of view templater
$this->view->products = $product;
$this->view->products = $search['products'];
if (isset($this->view->products) && count($this->view->products) > 0) {
// Amount of rendered products is more than 0
@ -228,13 +230,51 @@ final class catalog extends core
} else if (!isset($category)) {
// Not received identifier of the category
// search for root ascendants categories
$this->view->categories = entry::ascendants(
descendant: new category,
return: 'DISTINCT MERGE(ascendant, { name: ascendant.name.@language})',
parameters: ['language' => $this->language->name],
errors: $this->errors['catalog']
) ?? null;
if (isset($this->settings->catalog['categories']['structure']) && $this->settings->catalog['categories']['structure'] === 'pages') {
// Pages
// Searching for root ascendants categories
$this->view->categories = entry::ascendants(
descendant: new category,
return: 'DISTINCT MERGE(d, { name: d.name.@language})',
parameters: ['language' => $this->language->name],
errors: $this->errors['catalog']
) ?? null;
} else if (isset($this->settings->catalog['categories']['structure']) && $this->settings->catalog['categories']['structure'] === 'lists') {
// Lists
// Initializing the list of all categories
$categories = [];
// Initializing the structure of the list of all categories
$structure = [];
foreach (
entry::ascendants(
descendant: new category,
return: 'DISTINCT MERGE(d, { name: d.name.@language})',
parameters: ['language' => $this->language->name],
errors: $this->errors['catalog']
) ?? null as $document
) {
// Iterating over found root ascendants categories
// Generating the list (entering into recursion)
static::list(
document: $document,
language: $this->language,
categories: $categories,
structure: $structure,
errors: $this->errors['catalog']
);
}
// Writing the list of all categories into the templater variable
$this->view->categories = $categories;
// Writing the structure of the list of all categories into the templater variable
$this->view->structure = $structure;
}
}
// Validating @todo add throwing errors
@ -293,9 +333,12 @@ final class catalog extends core
parameters: ['language' => $this->language->name],
errors: $this->errors['menu']
);
// Universalizing
if ($this->view->menu instanceof _document) $this->view->menu = [$this->view->menu];
}
if (str_contains($this->request->headers['accept'], content::json->value)) {
if (str_contains($this->request->headers['accept'] ?? [], content::json->value)) {
// Request for JSON response
// Initializing the response body buffer
@ -384,4 +427,56 @@ final class catalog extends core
// Exit (success/fail)
return null;
}
/**
* List
*
*
*
* @return void
*/
private static function list(_document $document, language $language, array &$categories = [], array &$structure = [], array &$errors = []): void
{
// Initializing the object
$category = new category;
if (method_exists($category, '__document')) {
// Object can implement a document from ArangoDB
// Writing the instance of product document from ArangoDB to the implement object
$category->__document($document);
// Writing the category into the list of categories
$categories[$category->getId()] = $category;
// Writing the category into the structure of the list categories
$structure[$category->getId()] = [];
// Searching for entries that are descendants of $category
$search = model::search(
document: $category,
amount: 200,
categories_merge: 'name: v.name.@language',
parameters: ['language' => $language->name],
errors: $errors
);
if (count($search['categories']) > 0) {
// Found descendants categories
foreach ($search['categories'] as $document) {
// Iterating over descendants categories
// Entering into descendant level of the recursion for the list generation
static::list(
document: $document,
language: $language,
categories: $categories,
structure: $structure[$category->getId()],
errors: $errors
);
}
}
}
}
}

View File

@ -2,18 +2,18 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\controllers;
namespace mirzaev\huesos\controllers;
// Files of the project
use mirzaev\arming_bot\views\templater,
mirzaev\arming_bot\models\core as models,
mirzaev\arming_bot\models\account,
mirzaev\arming_bot\models\session,
mirzaev\arming_bot\models\settings,
mirzaev\arming_bot\models\cart,
mirzaev\arming_bot\models\suspension,
mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\enumerations\currency;
use mirzaev\huesos\views\templater,
mirzaev\huesos\models\core as models,
mirzaev\huesos\models\account,
mirzaev\huesos\models\session,
mirzaev\huesos\models\settings,
mirzaev\huesos\models\cart,
mirzaev\huesos\models\suspension,
mirzaev\huesos\models\enumerations\language,
mirzaev\huesos\models\enumerations\currency;
// Framework for PHP
use mirzaev\minimal\core as minimal,
@ -24,7 +24,7 @@ use mirzaev\minimal\core as minimal,
/**
* Core of controllers
*
* @package mirzaev\arming_bot\controllers
* @package mirzaev\huesos\controllers
*
* @param settings $settings Instance of the settings
* @param session $session Instance of the session
@ -167,7 +167,7 @@ class core extends controller
$this->account = $this->session->account($this->errors['account']);
// Initializing of the settings
$this->settings = settings::active();
$this->settings = settings::active(create: SETTINGS_PROJECT);
// Initializing of the language
$this->language = $this->account?->language ?? $this->session?->buffer['language'] ?? $this->settings?->language ?? language::en;

File diff suppressed because it is too large Load Diff

View File

@ -2,11 +2,11 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\controllers;
namespace mirzaev\huesos\controllers;
// Files of the project
use mirzaev\arming_bot\controllers\core,
mirzaev\arming_bot\models\menu;
use mirzaev\huesos\controllers\core,
mirzaev\huesos\models\menu;
// Framework for PHP
use mirzaev\minimal\http\enumerations\content;
@ -14,7 +14,7 @@ use mirzaev\minimal\http\enumerations\content;
/**
* Controller of pages
*
* @package mirzaev\arming_bot\controllers
* @package mirzaev\huesos\controllers
* @param array $errors Registry of errors
*
* @method null offer() Public offer
@ -56,6 +56,9 @@ final class index extends core
parameters: ['language' => $this->language->name],
errors: $this->errors['menu']
);
// Universalizing
if ($this->view->menu instanceof _document) $this->view->menu = [$this->view->menu];
}
if (str_contains($this->request->headers['accept'], content::any->value)) {

View File

@ -2,14 +2,14 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\controllers;
namespace mirzaev\huesos\controllers;
// Files of the project
use mirzaev\arming_bot\controllers\core,
mirzaev\arming_bot\models\cart as model,
mirzaev\arming_bot\models\product,
mirzaev\arming_bot\models\menu,
mirzaev\arming_bot\models\enumerations\language;
use mirzaev\huesos\controllers\core,
mirzaev\huesos\models\cart as model,
mirzaev\huesos\models\product,
mirzaev\huesos\models\menu,
mirzaev\huesos\models\enumerations\language;
// Framework for PHP
use mirzaev\minimal\http\enumerations\content,
@ -18,7 +18,7 @@ use mirzaev\minimal\http\enumerations\content,
/**
* Controller of cart
*
* @package mirzaev\arming_bot\controllers
* @package mirzaev\huesos\controllers
*
* @param model|null $cart Instance of the cart
* @param array $errors Registry of errors
@ -74,6 +74,9 @@ final class cart extends core
parameters: ['language' => $this->language->name],
errors: $this->errors['menu']
);
// Universalizing
if ($this->view->menu instanceof _document) $this->view->menu = [$this->view->menu];
}
// Initializing the cart

View File

@ -2,11 +2,11 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\controllers;
namespace mirzaev\huesos\controllers;
// Files of the project
use mirzaev\arming_bot\controllers\core,
mirzaev\arming_bot\models\account;
use mirzaev\huesos\controllers\core,
mirzaev\huesos\models\account;
// Framework for PHP
use mirzaev\minimal\http\enumerations\content,
@ -18,7 +18,7 @@ use mirzaev\arangodb\document;
/**
* Controller of session
*
* @package mirzaev\arming_bot\controllers
* @package mirzaev\huesos\controllers
*
* @param array $errors Registry of errors
*
@ -53,6 +53,7 @@ final class session extends core
* @param ?string $auth_date
* @param ?string $hash
* @param ?string $query_id
* @param ?string $signature
*
* @return null
*/
@ -62,7 +63,8 @@ final class session extends core
?string $chat_type = null,
?string $auth_date = null,
?string $hash = null,
?string $query_id = null
?string $query_id = null,
?string $signature = null
): null {
if (str_contains($this->request->headers['accept'], content::json->value)) {
// Request for JSON response
@ -70,6 +72,12 @@ final class session extends core
// Declaring variables in the correct scope
$identifier = $domain = $language = null;
// Initializing data of the account
$data = json_decode($user);
// Initializing avatar of the account
$avatar = $data->photo_url;
if ($connected = isset($this->account)) {
// Found the account
@ -84,10 +92,17 @@ final class session extends core
} else {
// Not found the account
if (isset($user, $chat_instance, $chat_type, $auth_date, $hash)) {
if (isset($user, $auth_date, $hash)) {
// Received required parameters
$buffer = ['user' => $user, 'chat_instance' => $chat_instance, 'chat_type' => $chat_type, 'auth_date' => $auth_date];
$buffer = ['user' => $user];
if (isset($query_id)) $buffer += ['query_id' => $query_id];
if (isset($signature)) $buffer += ['signature' => $signature];
if (isset($chat_instance)) $buffer += ['chat_instance' => $chat_instance];
if (isset($chat_type)) $buffer += ['chat_type' => $chat_type];
$buffer += ['auth_date' => $auth_date];
ksort($buffer);
@ -109,9 +124,6 @@ final class session extends core
if (time() - $auth_date < 86400) {
// Authorization date less than 1 day ago
// Initializing data of the account
$data = json_decode($user);
// Initializing of the account
$account = account::initialize(
$data->id,
@ -146,6 +158,9 @@ final class session extends core
// Initializing domain of the account
$domain = $account->domain;
// Initializing avatar of the account
$avatar = $data->photo_url;
}
}
}
@ -161,6 +176,7 @@ final class session extends core
'connected' => (bool) $connected,
'identifier' => $identifier ?? null,
'domain' => $domain ?? null,
'avatar' => $avatar ?? null,
'language' => $language?->name ?? null,
'errors' => $this->errors
])

View File

@ -2,18 +2,18 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
namespace mirzaev\huesos\models;
// Files of the project
use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\traits\status,
mirzaev\arming_bot\models\traits\buffer,
mirzaev\arming_bot\models\traits\cart,
mirzaev\arming_bot\models\traits\document as document_trait,
mirzaev\arming_bot\models\interfaces\document as document_interface,
mirzaev\arming_bot\models\interfaces\collection as collection_interface,
mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\enumerations\currency;
use mirzaev\huesos\models\core,
mirzaev\huesos\models\traits\status,
mirzaev\huesos\models\traits\buffer,
mirzaev\huesos\models\traits\cart,
mirzaev\huesos\models\traits\document as document_trait,
mirzaev\huesos\models\interfaces\document as document_interface,
mirzaev\huesos\models\interfaces\collection as collection_interface,
mirzaev\huesos\models\enumerations\language,
mirzaev\huesos\models\enumerations\currency;
// Framework for ArangoDB
use mirzaev\arangodb\collection,
@ -31,7 +31,7 @@ use exception;
/**
* Model of account
*
* @package mirzaev\arming_bot\models
* @package mirzaev\huesos\models
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
@ -86,7 +86,7 @@ final class account extends core implements document_interface, collection_inter
if (method_exists($account, '__document')) {
// Object can implement a document from ArangoDB
// Abstractioning of parameters
// Implementinf parameters
if (isset($result->language)) $result->language = language::{$result->language};
if (isset($result->currency)) $result->currency = currency::{$result->currency};
@ -121,7 +121,7 @@ final class account extends core implements document_interface, collection_inter
'messages' => $registration->getCanReadAllGroupMessages()
],
'premium' => $registration->isPremium(),
'language' => language::{$registration->getLanguageCode()}->name ?? 'en',
'language' => language::{$registration->getLanguageCode() ?? 'en'}?->name ?? 'en',
'queries' => [
'inline' => $registration->getSupportsInlineQueries()
]
@ -145,7 +145,7 @@ final class account extends core implements document_interface, collection_inter
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);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [

View File

@ -0,0 +1,260 @@
<?php
declare(strict_types=1);
namespace mirzaev\huesos\models\acquirings;
// Files of the project
use mirzaev\huesos\models\core,
mirzaev\huesos\models\settings,
mirzaev\huesos\models\cart,
mirzaev\huesos\models\order,
mirzaev\huesos\models\enumerations\currency;
// Framework for ArangoDB
use mirzaev\arangodb\collection,
mirzaev\arangodb\document;
// Library for ArangoDB
use ArangoDBClient\Document as _document;
// Built-in libraries
use exception,
DomainException as exception_domain,
RuntimeException as exception_runtime;
/**
* Model of robokassa
*
* @package mirzaev\huesos\models\acquirings
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class robokassa extends core
{
/**
* Link
*
* Generate the link to pay for the order
*
* @param order $order The order
* @param array &$errors Registry of errors
*
* @return string|null The link to pay for the order, if generated
*/
public static function link(order $order, array &$errors = []): ?string
{
try {
// Initializing the settings
$settings = settings::active();
if ($settings instanceof settings) {
// Initialized the settings
if ($settings->acquirings['robokassa']['enabled']) {
// Enabled the robokassa acquiring
// Declaring the robokassa settings
$robokassa = null;
// Initializing the test mode register
$test = 0;
if ($settings->acquirings['robokassa']['mode'] === 'work') {
// Work mode
// Initializing the robokassa settings
$robokassa = require(SETTINGS . DIRECTORY_SEPARATOR . 'acquirings' . DIRECTORY_SEPARATOR . 'robokassa' . DIRECTORY_SEPARATOR . 'work.php');
} else if ($settings->acquirings['robokassa']['mode'] === 'test') {
// Test mode
// Reinitializing the test mode register
$test = 1;
// Initializing the robokassa settings
$robokassa = require(SETTINGS . DIRECTORY_SEPARATOR . 'acquirings' . DIRECTORY_SEPARATOR . 'robokassa' . DIRECTORY_SEPARATOR . 'test.php');
} else {
// Failed to initialized the mode
// Exit (fail)
throw new exception_domain('Failed to initialize the robokassa mode');
}
// Initializing the cart
$cart = $order->cart();
if ($cart instanceof cart) {
// Initialized the cart
// Initializing robokassa parameters
$login = $robokassa['login'];
$password = $robokassa['passwords'][0];
// Initializing the order parameters
$identifier = $order->getKey();
// Initializing the invoice parameters
$description = "Оплата заказа";
$cost = $cart->cost() + ($cart->buffer['delivery']['cost'] ?? 0);
// Generatings the MD5 hash
$md5 = md5("$login:$cost:$identifier:$password");
/*
// Writing the MD5 hash into the order implementator
$order->acquirings = ['robokassa' => ['hash' => $md5] + ($order->acquirings['robokassa'] ?? [])] + ($order->acquirings ?? []);
if (document::update($order->__document(), errors: $errors)) {
// Writed to ArangoDB */
// Exit (success)
return "https://auth.robokassa.ru/Merchant/Index.aspx?MerchantLogin=$login&OutSum=$cost&InvId=$identifier&Description=$description&SignatureValue=$md5&IsTest=$test";
/* } else throw new exception('Failed to write the robokassa hash into the order'); */
} else {
// Not initialized the cart
// Exit (fail)
throw new exception_runtime('Failed to initialize the cart');
}
}
}
} 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;
}
/**
* Verify (result)
*
* Generate hash by arguments and verify for result with the hash
*
* @param int $identifier Identifier of the order
* @param string $cost Cost of the order (can be with two zeros, like 1000.00)
* @param string $hash Hash of the order
* @param array &$errors Registry of errors
*
* @return string|null The link to pay for the order, if generated
*/
public static function result(int $identifier, string $cost, string $hash, bool $test = false, array &$errors = []): ?bool
{
try {
// Initializing the settings
$settings = settings::active();
if ($settings instanceof settings) {
// Initialized the settings
if ($settings->acquirings['robokassa']['enabled']) {
// Enabled the robokassa acquiring
// Declaring the robokassa settings
$robokassa = null;
if ($test) {
// Test mode
// Initializing the robokassa settings
$robokassa = require(SETTINGS . DIRECTORY_SEPARATOR . 'acquirings' . DIRECTORY_SEPARATOR . 'robokassa' . DIRECTORY_SEPARATOR . 'test.php');
} else {
// Work mode
// Initializing the robokassa settings
$robokassa = require(SETTINGS . DIRECTORY_SEPARATOR . 'acquirings' . DIRECTORY_SEPARATOR . 'robokassa' . DIRECTORY_SEPARATOR . 'work.php');
}
// Initializing robokassa parameters
$password = $robokassa['passwords'][1];
// Generatings the MD5 hash
$md5 = strtoupper(md5("$cost:$identifier:$password"));
// Exit (success)
return $md5 === $hash;
}
}
} 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;
}
/**
* Verify (success)
*
* Generate hash by arguments and verify for success with the hash
*
* @param int $identifier Identifier of the order
* @param string $cost Cost of the order (can be with two zeros, like 1000.00)
* @param string $hash Hash of the order
* @param array &$errors Registry of errors
*
* @return string|null The link to pay for the order, if generated
*/
public static function success(int $identifier, string $cost, string $hash, bool $test = false, array &$errors = []): ?bool
{
try {
// Initializing the settings
$settings = settings::active();
if ($settings instanceof settings) {
// Initialized the settings
if ($settings->acquirings['robokassa']['enabled']) {
// Enabled the robokassa acquiring
// Declaring the robokassa settings
$robokassa = null;
if ($test) {
// Test mode
// Initializing the robokassa settings
$robokassa = require(SETTINGS . DIRECTORY_SEPARATOR . 'acquirings' . DIRECTORY_SEPARATOR . 'robokassa' . DIRECTORY_SEPARATOR . 'test.php');
} else {
// Work mode
// Initializing the robokassa settings
$robokassa = require(SETTINGS . DIRECTORY_SEPARATOR . 'acquirings' . DIRECTORY_SEPARATOR . 'robokassa' . DIRECTORY_SEPARATOR . 'work.php');
}
// Initializing robokassa parameters
$password = $robokassa['passwords'][0];
// Generatings the MD5 hash
$md5 = md5("$cost:$identifier:$password");
// Exit (success)
return $md5 === $hash;
}
}
} 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

@ -2,17 +2,17 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
namespace mirzaev\huesos\models;
// Files of the project
use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\reservation,
mirzaev\arming_bot\models\traits\buffer,
mirzaev\arming_bot\models\traits\document as document_trait,
mirzaev\arming_bot\models\interfaces\document as document_interface,
mirzaev\arming_bot\models\interfaces\collection as collection_interface,
mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\enumerations\currency;
use mirzaev\huesos\models\core,
mirzaev\huesos\models\reservation,
mirzaev\huesos\models\traits\buffer,
mirzaev\huesos\models\traits\document as document_trait,
mirzaev\huesos\models\interfaces\document as document_interface,
mirzaev\huesos\models\interfaces\collection as collection_interface,
mirzaev\huesos\models\enumerations\language,
mirzaev\huesos\models\enumerations\currency;
// Framework for ArangoDB
use mirzaev\arangodb\collection,
@ -22,13 +22,14 @@ use mirzaev\arangodb\collection,
use ArangoDBClient\Document as _document;
// Built-in libraries
use exception;
use DateTime as datetime,
Exception as exception;
/**
* Model of cart
*
* @uses reservation
* @package mirzaev\arming_bot\models
* @package mirzaev\huesos\models
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
@ -110,9 +111,9 @@ final class cart extends core implements document_interface, collection_interfac
// Exit (success)
return isset($products['amount']) ? [$products['document']['_id'] => $products] : $products;
} else throw new exception('Failed to initialize ' . product::TYPE . ' collection: ' . product::COLLECTION);
} else throw new exception('Failed to initialize ' . reservation::TYPE . ' collection: ' . reservation::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . product::TYPE->name . ' collection: ' . product::COLLECTION);
} else throw new exception('Failed to initialize ' . reservation::TYPE->name . ' collection: ' . reservation::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
@ -150,11 +151,11 @@ final class cart extends core implements document_interface, collection_interfac
// Search for all products in the cart
$result = @collection::execute(
<<<AQL
FOR v IN 1..1 INBOUND @cart GRAPH @graph
FILTER IS_SAME_COLLECTION(@collection, v._id)
COLLECT AGGREGATE amount = COUNT(v), cost = SUM(v.cost.@currency)
RETURN { amount, cost }
AQL,
FOR v IN 1..1 INBOUND @cart GRAPH @graph
FILTER IS_SAME_COLLECTION(@collection, v._id)
COLLECT AGGREGATE amount = COUNT(v), cost = SUM(v.cost.@currency)
RETURN { amount, cost }
AQL,
[
'graph' => 'catalog',
'cart' => $this->getId(),
@ -167,9 +168,9 @@ final class cart extends core implements document_interface, collection_interfac
// Exit (success)
return $result;
} else throw new exception('Failed to initialize ' . product::TYPE . ' collection: ' . product::COLLECTION);
} else throw new exception('Failed to initialize ' . reservation::TYPE . ' collection: ' . reservation::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . product::TYPE->name . ' collection: ' . product::COLLECTION);
} else throw new exception('Failed to initialize ' . reservation::TYPE->name . ' collection: ' . reservation::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
@ -184,7 +185,6 @@ final class cart extends core implements document_interface, collection_interfac
return null;
}
/**
* Count
*
@ -222,9 +222,9 @@ final class cart extends core implements document_interface, collection_interfac
],
errors: $errors
);
} else throw new exception('Failed to initialize ' . product::TYPE . ' collection: ' . product::COLLECTION);
} else throw new exception('Failed to initialize ' . reservation::TYPE . ' collection: ' . reservation::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . product::TYPE->name . ' collection: ' . product::COLLECTION);
} else throw new exception('Failed to initialize ' . reservation::TYPE->name . ' collection: ' . reservation::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
@ -242,7 +242,7 @@ final class cart extends core implements document_interface, collection_interfac
/*
* Write
*
* Write the product in the cart
* Write the product into the cart
*
* @param product $product The product
* @param int $amount Amount of writings
@ -273,9 +273,9 @@ final class cart extends core implements document_interface, collection_interfac
],
errors: $errors
);
} else throw new exception('Failed to initialize ' . product::TYPE . ' collection: ' . product::COLLECTION);
} else throw new exception('Failed to initialize ' . reservation::TYPE . ' collection: ' . reservation::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . product::TYPE->name . ' collection: ' . product::COLLECTION);
} else throw new exception('Failed to initialize ' . reservation::TYPE->name . ' collection: ' . reservation::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
@ -324,9 +324,9 @@ final class cart extends core implements document_interface, collection_interfac
],
errors: $errors
);
} else throw new exception('Failed to initialize ' . product::TYPE . ' collection: ' . product::COLLECTION);
} else throw new exception('Failed to initialize ' . reservation::TYPE . ' collection: ' . reservation::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . product::TYPE->name . ' collection: ' . product::COLLECTION);
} else throw new exception('Failed to initialize ' . reservation::TYPE->name . ' collection: ' . reservation::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
@ -362,7 +362,7 @@ final class cart extends core implements document_interface, collection_interfac
// Exit (success)
return $this->share;
} else throw new exception('Failed to write confirmed cart to ArangoDB');
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
@ -401,7 +401,7 @@ final class cart extends core implements document_interface, collection_interfac
// Exit (success)
return true;
} else throw new exception('Failed to write confirmed cart to ArangoDB');
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
@ -416,26 +416,50 @@ final class cart extends core implements document_interface, collection_interfac
return false;
}
/*
* Order
*
/**
* Account
*
* Search for the connected account
*
* @param array &$errors Registry of errors
*
* @return void
* @return account|null The connected account, if found
*/
public function order(array &$errors = []): void
public function account(array &$errors = []): ?account
{
try {
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) {
if (collection::initialize(order::COLLECTION, order::TYPE, errors: $errors)) {
// Initialized collections
if ($result = collection::execute(
<<<'AQL'
FOR v IN 1..1 OUTBOUND @cart GRAPH users
FILTER PARSE_IDENTIFIER(v._id).collection == @account
LIMIT 1
return v
AQL,
[
'cart' => $this->document->getId(),
'account' => account::COLLECTION
],
errors: $errors
)) {
// Found the instance of the account connected to the account
} else throw new exception('Failed to initialize ' . product::TYPE . ' collection: ' . product::COLLECTION);
} else throw new exception('Failed to initialize ' . reservation::TYPE . ' collection: ' . reservation::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
// Initializing the object
$account = new account;
if (method_exists($account, '__document')) {
// Object can implement a document from ArangoDB
// Implementinf parameters
if (isset($result->language)) $result->language = language::{$result->language};
if (isset($result->currency)) $result->currency = currency::{$result->currency};
// Writing the instance of the account document from ArangoDB to the implement object
$account->__document($result);
// Exit (success)
return $account;
}
}
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
@ -445,5 +469,221 @@ final class cart extends core implements document_interface, collection_interfac
'stack' => $e->getTrace()
];
}
// Exit (fail)
return null;
}
/**
* Cost
*
* Generate the cart products cost
*
* @param string &$list List of the calculated products
* @param array &$errors Registry of errors
*
* @return float|int|null The cart products cost, if generated
*/
public function cost(string &$list = '', array &$errors = []): float|int|null
{
try {
// Initializing the account
$account = $this->account($errors);
if ($account instanceof account) {
// Initialized the account
// Initializing products in the cart
$products = $this->products(language: $account->language ?? language::ru, currency: $account->currency ?? currency::rub);
if (!empty($products)) {
// Initialized products in the cart
// Declaring total cost of products
$cost = 0;
// Initializing iterator of rows
$row = 0;
foreach ($products as $product) {
// Iterating over products
// Generating formatted list of products for message
$list .= ++$row . '. ' . $product['document']['name'] . ' (' . $product['amount'] . 'шт)' . "\n";
// Generating total cost of products
$cost += $product['document']['cost'] * $product['amount'];
}
// Exit (success)
return $cost;
}
}
} 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;
}
/*
* Order
*
* Create the order and connect to the cart and to the account
* Make the cart ordered (the account will create a new cart)
*
* @param array &$errors Registry of errors
*
* @return order|null The order, if created
*
* @todo Handling errors
*/
public function order(array &$errors = []): ?order
{
try {
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
if (collection::initialize(order::COLLECTION, order::TYPE, errors: $errors)) {
if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) {
if (collection::initialize(account::COLLECTION, account::TYPE, errors: $errors)) {
// Initialized collections
// Searching for the order
$existed = collection::execute(
<<<'AQL'
FOR v IN 1..1 OUTBOUND @cart GRAPH users
FILTER PARSE_IDENTIFIER(v._id).collection == @order
LIMIT 1
return v
AQL,
[
'cart' => $this->document->getId(),
'order' => order::COLLECTION
],
errors: $errors
);
if ($existed) {
// The order has already been created
// Initializing the object
$order = new order;
if (method_exists($order, '__document')) {
// Object can implement a document from ArangoDB
// Writing the instance of the order document from ArangoDB to the implement object
$order->__document($existed);
// Exit (success)
return $order;
}
} else {
// The order has not been created
// Initializing a new order
$_id = document::write(
order::COLLECTION,
[
'active' => true,
/* 'term' => (int) new datetime('+15 minutes')->ormat('U') */
]
);
if ($result = collection::execute(
<<<'AQL'
FOR d IN @@collection
FILTER d._id == @_id && d.active == true
RETURN d
AQL,
[
'@collection' => order::COLLECTION,
'_id' => $_id
],
errors: $errors
)) {
// Found the instance of just created the new order
// Writing the ordered status for the cart
$this->document->ordered = true;
if (document::update($this->__document(), errors: $errors)) {
// Writed into ArangoDB
// Initializing the object
$order = new order;
if (method_exists($order, '__document')) {
// Object can implement a document from ArangoDB
// Writing the instance of the order document from ArangoDB to the implement object
$order->__document($result);
// Connecting the cart to the order
$connected = $order->connect($this, $errors);
if ($connected) {
// Connected the cart with the order
if ($result = collection::execute(
<<<'AQL'
FOR v IN 1..1 OUTBOUND @cart GRAPH users
FILTER PARSE_IDENTIFIER(v._id).collection == @account
LIMIT 1
return v
AQL,
[
'cart' => $this->document->getId(),
'account' => account::COLLECTION
],
errors: $errors
)) {
// Found the instance of the account connected to the cart
// Initializing the object
$account = new account;
if (method_exists($account, '__document')) {
// Object can implement a document from ArangoDB
// Writing the instance of the account document from ArangoDB to the implement object
$account->__document($result);
// Connecting the account with the order
$connected = $account->connect($order, $errors);
if ($connected) {
// Connected the account with the order
// Exit (success)
return $order;
}
}
}
}
} else throw new exception('Class ' . order::class . ' does not implement a document from ArangoDB');
} else throw new exception('Failed to write the ordered status for the cart');
} else throw new exception('Failed to create or find just created ' . static::class);
}
} else throw new exception('Failed to initialize ' . account::TYPE->name . ' collection: ' . account::COLLECTION);
} else throw new exception('Failed to initialize ' . reservation::TYPE->name . ' collection: ' . reservation::COLLECTION);
} else throw new exception('Failed to initialize ' . order::TYPE->name . ' collection: ' . order::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' 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

@ -2,17 +2,17 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
namespace mirzaev\huesos\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\enumerations\language,
mirzaev\arming_bot\models\enumerations\currency,
mirzaev\arming_bot\models\traits\yandex\disk as yandex;
use mirzaev\huesos\models\core,
mirzaev\huesos\models\product,
mirzaev\huesos\models\category,
mirzaev\huesos\models\entry,
mirzaev\huesos\models\traits\files,
mirzaev\huesos\models\enumerations\language,
mirzaev\huesos\models\enumerations\currency,
mirzaev\huesos\models\traits\yandex\disk as yandex;
// Framework for PHP
use mirzaev\minimal\http\enumerations\content;
@ -33,7 +33,7 @@ use GdImage as image;
/**
* Model of the catalog
*
* @package mirzaev\arming_bot\models
* @package mirzaev\huesos\models
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
@ -41,7 +41,64 @@ use GdImage as image;
final class catalog extends core
{
use yandex, files {
yandex::download as yandex;
yandex::download as file;
yandex::list as folder;
}
/**
* Search
*
* @param category|product $document Ascendant document
* @param string|null $filter Expression for filtering (AQL)
* @param string|null $sort Expression for sorting (AQL)
* @param int $depth Amount of nodes for traversal search (subcategories/products inside subcategories...)
* @param int $page Page
* @param int $amount Amount of documents per page
* @param string|null $categories_merge Expression with paremeters to return for categories (AQL)
* @param string|null $products_merge Expression with paremeters to return for products (AQL)
* @param array $parameters Binded parameters for placeholders ['placeholder' => parameter]
* @param array &$errors Registry of errors
*
* @return array [categories => [document::class...], $products => [document::class...]]
*/
public static function search(
category|product $document,
?string $filter = 'v.deleted != true && v.hidden != true',
?string $sort = 'v.position ASC, v.created DESC',
int $depth = 1,
int $page = 1,
int $amount = 100,
?string $categories_merge = null,
?string $products_merge = null,
array $parameters = [],
array &$errors = []
): array {
// Search for entries that are descendants of $category
$entries = entry::search(
document: $document,
filter: $filter,
sort: $sort,
depth: $depth,
page: $page,
amount: $amount,
categories_merge: $categories_merge,
products_merge: $products_merge,
parameters: $parameters,
errors: $errors
);
// Initialize buffers of entries (in singular, by parameter from ArangoDB)
$category = $product = [];
foreach ($entries as $entry) {
// Iterate over entries (descendands)
// Write entry to the buffer of entries (sort by $category and $product)
${$entry->_type}[] = $entry;
}
// Exit (success)
return ['categories' => $category, 'products' => $product];
}
/**
@ -120,7 +177,7 @@ final class catalog extends core
// Iterate over categories
try {
if (!empty($row['identifier']) && !empty($row['name'])) {
if (!empty($row['identifier']) && is_int($row['identifier']) && $row['identifier'] > 0 && !empty($row['name'])) {
// Required cells are filled in
// Incrementing the counter of loaded categories
@ -195,7 +252,10 @@ final class catalog extends core
// Received images
// Initializing new images of the category
$images = explode(' ', mb_trim($row['images']));
$images = static::folder(
uri: explode(' ', mb_trim($row['images']))[0],
errors: $errors
);
// Reinitialize images? (true, if no images found or their amount does not match)
/* $reinitialize = !$category->images || count($category->images) !== count($images); */
@ -210,14 +270,14 @@ final class catalog extends core
// Initializing the buffer of images
$buffer = [];
foreach ($images as $index => $file) {
foreach (is_array($images) ? $images : [] as $image) {
// Iterating over new images
// Skipping empty URI`s
if (empty($file = mb_trim($file))) continue;
// Initializing identifier of the image
$identifier = preg_replace('/\.\w+$/', '', $image->name);
// Initializing path to directory of images in storage
$directory = DIRECTORY_SEPARATOR . 'categories' . DIRECTORY_SEPARATOR . $row['identifier'] . DIRECTORY_SEPARATOR . $index;
$directory = DIRECTORY_SEPARATOR . 'categories' . DIRECTORY_SEPARATOR . $row['identifier'] . DIRECTORY_SEPARATOR . $identifier;
// Initializing URL of the image in storage
$url = STORAGE . $directory;
@ -225,8 +285,8 @@ final class catalog extends core
// Initializing the directory in storage
if (!file_exists($url)) mkdir($url, 0775, true);
if ($downloaded = static::yandex(
uri: $file,
if ($downloaded = static::file(
uri: $image->public_url ?? $image->public_key,
destination: $url,
name: 'source',
errors: $errors
@ -249,6 +309,11 @@ final class catalog extends core
// Initializing implementator of the image
$boba = imagecreatefromjpeg($uri);
} else if ($downloaded['content'] === content::webp) {
// WEBP
// Initializing implementator of the image
$boba = imagecreatefromwebp($uri);
}
// Enabling better antialiasing
@ -280,10 +345,10 @@ final class catalog extends core
imagecopyresampled($biba, $boba, 0, 0, 0, 0, $width, $height, $size[0], $size[1]);
// Initializing URI of the resized image
$uri = $directory . DIRECTORY_SEPARATOR . "$resize." . $downloaded['content']->extension();
$uri = $directory . DIRECTORY_SEPARATOR . "$resize.webp";
// Saving the image
imagePng($biba, STORAGE . $uri);
imagewebp($biba, STORAGE . $uri);
// Writing the resized image to the buffer of resized images
$resized[$resize] = $uri;
@ -291,7 +356,7 @@ final class catalog extends core
// Writing the image to the buffer if images
$buffer[] = [
'source' => $file,
'source' => $image->public_url ?? null,
'storage' => [
'source' => $directory . DIRECTORY_SEPARATOR . $downloaded['name'] . '.' . $downloaded['content']->extension(),
] + $resized
@ -349,7 +414,7 @@ final class catalog extends core
// Iterate over products
try {
if (!empty($row['identifier']) && !empty($row['name'])) {
if (!empty($row['identifier']) && is_int($row['identifier']) && $row['identifier'] > 0 && !empty($row['name'])) {
// Required cells are filled in
// Incrementing the counter of loaded products
@ -385,7 +450,7 @@ final class catalog extends core
// Initializing position of the product
if (empty($product->position) || $product->position !== $row['position'])
$product->position = $row['position'];
$product->position = isset($row['position']) ? (int) $row['position'] : 0;
} else {
// Not initialized the product
@ -399,7 +464,7 @@ final class catalog extends core
dimensions: ['x' => $row['x'], 'y' => $row['y'], 'z' => $row['z']],
brand: [$language->name => $row['brand']],
compatibility: [$language->name => $row['compatibility']],
position: (int) $row['position'] ?? null,
position: isset($row['position']) ? (int) $row['position'] : 0,
errors: $errors
);
@ -436,8 +501,11 @@ final class catalog extends core
if (!empty($row['images'])) {
// Received images
// Initializing new images of the category
$images = explode(' ', mb_trim($row['images']));
// Initializing new images of the product
$images = static::folder(
uri: explode(' ', mb_trim($row['images']))[0],
errors: $errors
);
// Reinitialize images? (true, if no images found or their amount does not match)
/* $reinitialize = !$product->images || count($product->images) !== count($images); */
@ -452,14 +520,14 @@ final class catalog extends core
// Initializing the buffer of images
$buffer = [];
foreach ($images as $index => $file) {
foreach (is_array($images) ? $images : [] as $image) {
// Iterating over new images
// Skipping empty URI`s
if (empty($file = mb_trim($file))) continue;
// Initializing identifier of the image
$identifier = preg_replace('/\.\w+$/', '', $image->name);
// Initializing path to directory of images in storage
$directory = DIRECTORY_SEPARATOR . 'products' . DIRECTORY_SEPARATOR . $row['identifier'] . DIRECTORY_SEPARATOR . $index;
$directory = DIRECTORY_SEPARATOR . 'products' . DIRECTORY_SEPARATOR . $row['identifier'] . DIRECTORY_SEPARATOR . $identifier;
// Initializing URL of the image in storage
$url = STORAGE . $directory;
@ -467,8 +535,8 @@ final class catalog extends core
// Initializing the directory in storage
if (!file_exists($url)) mkdir($url, 0775, true);
if ($downloaded = static::yandex(
uri: $file,
if ($downloaded = static::file(
uri: $image->public_url ?? $image->public_key,
destination: $url,
name: 'source',
errors: $errors
@ -491,6 +559,11 @@ final class catalog extends core
// Initializing implementator of the image
$boba = imagecreatefromjpeg($uri);
} else if ($downloaded['content'] === content::webp) {
// WEBP
// Initializing implementator of the image
$boba = imagecreatefromwebp($uri);
}
// Enabling better antialiasing
@ -522,10 +595,10 @@ final class catalog extends core
imagecopyresampled($biba, $boba, 0, 0, 0, 0, $width, $height, $size[0], $size[1]);
// Initializing URI of the resized image
$uri = $directory . DIRECTORY_SEPARATOR . "$resize." . $downloaded['content']->extension();
$uri = $directory . DIRECTORY_SEPARATOR . "$resize.webp";
// Saving the image
imagePng($biba, STORAGE . $uri);
imagewebp($biba, STORAGE . $uri);
// Writing the resized image to the buffer of resized images
$resized[$resize] = $uri;
@ -533,7 +606,7 @@ final class catalog extends core
// Writing the image to the buffer if images
$buffer[] = [
'source' => $file,
'source' => $image->public_url ?? null,
'storage' => [
'source' => $directory . DIRECTORY_SEPARATOR . $downloaded['name'] . '.' . $downloaded['content']->extension(),
] + $resized

View File

@ -2,13 +2,13 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
namespace mirzaev\huesos\models;
// Files of the project
use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\traits\document as document_trait,
mirzaev\arming_bot\models\interfaces\document as document_interface,
mirzaev\arming_bot\models\interfaces\collection as collection_interface;
use mirzaev\huesos\models\core,
mirzaev\huesos\models\traits\document as document_trait,
mirzaev\huesos\models\interfaces\document as document_interface,
mirzaev\huesos\models\interfaces\collection as collection_interface;
// Framework for ArangoDB
use mirzaev\arangodb\collection,
@ -21,7 +21,7 @@ use exception;
/**
* Model of category
*
* @package mirzaev\arming_bot\models
* @package mirzaev\huesos\models
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
@ -69,7 +69,7 @@ final class category extends core implements document_interface, collection_inte
],
errors: $errors
);
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [

View File

@ -2,13 +2,13 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
namespace mirzaev\huesos\models;
// Files of the project
use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\traits\document as document_trait,
mirzaev\arming_bot\models\interfaces\document as document_interface,
mirzaev\arming_bot\models\interfaces\collection as collection_interface;
use mirzaev\huesos\models\core,
mirzaev\huesos\models\traits\document as document_trait,
mirzaev\huesos\models\interfaces\document as document_interface,
mirzaev\huesos\models\interfaces\collection as collection_interface;
// Framework for ArangoDB
use mirzaev\arangodb\enumerations\collection\type;
@ -16,7 +16,7 @@ use mirzaev\arangodb\enumerations\collection\type;
/**
* Model of connect
*
* @package mirzaev\arming_bot\models
* @package mirzaev\huesos\models
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
namespace mirzaev\huesos\models;
// Framework for PHP
use mirzaev\minimal\model;
@ -20,7 +20,7 @@ use exception;
/**
* Core of models
*
* @package mirzaev\arming_bot\models
* @package mirzaev\huesos\models
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
@ -127,7 +127,7 @@ class core extends model
// Exit (success)
return $result;
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to registry of errors
$errors[] = [

View File

@ -2,13 +2,13 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models\deliveries;
namespace mirzaev\huesos\models\deliveries;
// Files of the project
use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\traits\document as document_trait,
mirzaev\arming_bot\models\interfaces\document as document_interface,
mirzaev\arming_bot\models\interfaces\collection as collection_interface;
use mirzaev\huesos\models\core,
mirzaev\huesos\models\traits\document as document_trait,
mirzaev\huesos\models\interfaces\document as document_interface,
mirzaev\huesos\models\interfaces\collection as collection_interface;
// The HTTP PSR-18 adapter for Guzzle HTTP client
use Http\Adapter\Guzzle7\Client as guzzle;
@ -30,7 +30,7 @@ use exception,
/**
* Model of CDEK
*
* @package mirzaev\arming_bot\models\deliveries
* @package mirzaev\huesos\models\deliveries
*
* @method cities|null location(string $name, array &$errors) Search for CDEK location by name
*

View File

@ -2,11 +2,11 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
namespace mirzaev\huesos\models;
// Files of the project
use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\enumerations\delivery as company;
use mirzaev\huesos\models\core,
mirzaev\huesos\models\enumerations\delivery as company;
// Built-in libraries
use exception;
@ -14,7 +14,7 @@ use exception;
/**
* Model of settings
*
* @package mirzaev\arming_bot\models
* @package mirzaev\huesos\models
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>

View File

@ -2,13 +2,13 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
namespace mirzaev\huesos\models;
// Files of the project
use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\traits\document as document_trait,
mirzaev\arming_bot\models\interfaces\document as document_interface,
mirzaev\arming_bot\models\interfaces\collection as collection_interface;
use mirzaev\huesos\models\core,
mirzaev\huesos\models\traits\document as document_trait,
mirzaev\huesos\models\interfaces\document as document_interface,
mirzaev\huesos\models\interfaces\collection as collection_interface;
// Library for ArangoDB
use ArangoDBClient\Document as _document;
@ -24,7 +24,7 @@ use exception;
/**
* Model of entry
*
* @package mirzaev\arming_bot\models
* @package mirzaev\huesos\models
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
@ -73,9 +73,9 @@ final class entry extends core implements document_interface, collection_interfa
],
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);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . $to::TYPE->name . ' collection: ' . $to::COLLECTION);
} else throw new exception('Failed to initialize ' . $from::TYPE->name . ' collection: ' . $from::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
@ -112,15 +112,20 @@ final class entry extends core implements document_interface, collection_interfa
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
// Initialized the collection
// Search for ascendants
// Search for ascendants
if ($result = collection::execute(
sprintf(
<<<'AQL'
FOR d IN @@collection
FOR ascendant IN OUTBOUND d @@edge
RETURN %s
let from = (
FOR e IN @@edge
RETURN DISTINCT e._from
)
FOR d in @@collection
FILTER !POSITION(from, d._id)
RETURN %s
AQL,
empty($return) ? 'DISTINCT ascendant' : $return
empty($return) ? 'DISTINCT d' : $return
),
[
'@collection' => $descendant::COLLECTION,
@ -133,7 +138,7 @@ final class entry extends core implements document_interface, collection_interfa
// Exit (success)
return is_array($result) ? $result : [$result];
} else return [];
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
@ -189,9 +194,9 @@ final class entry extends core implements document_interface, collection_interfa
// 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);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . $to::TYPE->name . ' collection: ' . $to::COLLECTION);
} else throw new exception('Failed to initialize ' . $from::TYPE->name . ' collection: ' . $from::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
@ -218,8 +223,9 @@ final class entry extends core implements document_interface, collection_interfa
* @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 int $depth Amount of nodes for traversal search (subcategories/products inside subcategories...)
* @param int $page Page
* @param int $amount Amount of documents per page
* @param string|null $categories_merge Expression with paremeters to return for categories (AQL)
* @param string|null $products_merge Expression with paremeters to return for products (AQL)
* @param array $parameters Binded parameters for placeholders ['placeholder' => parameter]
@ -231,6 +237,7 @@ final class entry extends core implements document_interface, collection_interfa
category|product $document,
?string $filter = 'v.deleted != true && v.hidden != true',
?string $sort = 'v.position ASC, v.created DESC',
int $depth = 1,
int $page = 1,
int $amount = 100,
?string $categories_merge = null,
@ -247,12 +254,13 @@ final class entry extends core implements document_interface, collection_interfa
return is_array($result = collection::execute(
sprintf(
<<<'AQL'
FOR v IN 1..1 INBOUND @document GRAPH @graph
FOR v IN 1..%u INBOUND @document GRAPH @graph
%s
%s
LIMIT @offset, @amount
RETURN DISTINCT IS_SAME_COLLECTION(@category, v._id) ? MERGE(v, {_type: @category%s}) : MERGE(v, {_type: @product%s})
AQL,
$depth,
empty($filter) ? '' : "FILTER $filter",
empty($sort) ? '' : "SORT $sort",
empty($categories_merge) ? '' : ", $categories_merge",
@ -268,8 +276,8 @@ final class entry extends core implements document_interface, collection_interfa
] + $parameters,
errors: $errors
)) ? $result : [$result];
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . $document::TYPE . ' collection: ' . $document::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . $document::TYPE->name . ' collection: ' . $document::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
@ -315,8 +323,8 @@ final class entry extends core implements document_interface, collection_interfa
],
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);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . $document::TYPE->name . ' collection: ' . $document::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [

View File

@ -2,15 +2,15 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models\enumerations;
namespace mirzaev\huesos\models\enumerations;
// Files of the project
use mirzaev\arming_bot\models\enumerations\language;
use mirzaev\huesos\models\enumerations\language;
/**
* Types of currencies by ISO 4217 standart
*
* @package mirzaev\arming_bot\models\enumerations
* @package mirzaev\huesos\models\enumerations
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>

View File

@ -2,12 +2,12 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models\enumerations;
namespace mirzaev\huesos\models\enumerations;
/**
* Types of deliveries
*
* @package mirzaev\arming_bot\models\enumerations
* @package mirzaev\huesos\models\enumerations
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace mirzaev\huesos\models\enumerations;
// Files of the project
use mirzaev\huesos\models\enumerations\language;
/**
* Types of destination
*
* @package mirzaev\huesos\models\enumerations
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
enum destination
{
case office;
case door;
/**
* Label
*
* Initialize label of the destination
*
* @param language|null $language Language into which to translate
*
* @return string Translated label of the destination
*/
public function label(?language $language = language::en): string
{
// Exit (success)
return match ($this) {
destination::office => match ($language) {
language::en => 'Office',
language::ru => 'Пункт выдачи'
},
destination::door => match ($language) {
language::en => 'Door',
language::ru => 'До двери'
}
};
}
}

View File

@ -2,12 +2,12 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models\enumerations;
namespace mirzaev\huesos\models\enumerations;
/**
* Types of languages by ISO 639-1 standart
*
* @package mirzaev\arming_bot\models\enumerations
* @package mirzaev\huesos\models\enumerations
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>

View File

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

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models\interfaces;
namespace mirzaev\huesos\models\interfaces;
// Framework for ArangoDB
use mirzaev\arangodb\enumerations\collection\type;
@ -10,7 +10,7 @@ use mirzaev\arangodb\enumerations\collection\type;
/**
* Interface for implementing a collection from ArangoDB
*
* @package mirzaev\arming_bot\models\traits
* @package mirzaev\huesos\models\traits
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models\interfaces;
namespace mirzaev\huesos\models\interfaces;
// Library для ArangoDB
use ArangoDBClient\Document as _document;
@ -12,7 +12,7 @@ use ArangoDBClient\Document as _document;
*
* @param _document $document An instance of the ArangoDB document from ArangoDB (protected readonly)
*
* @package mirzaev\arming_bot\models\traits
* @package mirzaev\huesos\models\traits
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>

View File

@ -2,18 +2,18 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
namespace mirzaev\huesos\models;
// Files of the project
use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\traits\document as document_trait,
mirzaev\arming_bot\models\interfaces\document as document_interface,
mirzaev\arming_bot\models\interfaces\collection as collection_interface;
use mirzaev\huesos\models\core,
mirzaev\huesos\models\traits\document as document_trait,
mirzaev\huesos\models\interfaces\document as document_interface,
mirzaev\huesos\models\interfaces\collection as collection_interface;
/**
* Model of menu
*
* @package mirzaev\arming_bot\models
* @package mirzaev\huesos\models
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>

View File

@ -0,0 +1,156 @@
<?php
declare(strict_types=1);
namespace mirzaev\huesos\models;
// Files of the project
use mirzaev\huesos\models\core,
mirzaev\huesos\models\cart,
mirzaev\huesos\models\account,
mirzaev\huesos\models\reservation,
mirzaev\huesos\models\traits\document as document_trait,
mirzaev\huesos\models\interfaces\document as document_interface,
mirzaev\huesos\models\interfaces\collection as collection_interface,
mirzaev\huesos\models\enumerations\language,
mirzaev\huesos\models\enumerations\currency;
// Framework for ArangoDB
use mirzaev\arangodb\collection,
mirzaev\arangodb\document;
// Library for ArangoDB
use ArangoDBClient\Document as _document;
// Built-in libraries
use exception;
/**
* Model of order
*
* @uses !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* @package mirzaev\huesos\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 order extends core implements document_interface, collection_interface
{
use document_trait;
/**
* Name of the collection in ArangoDB
*/
final public const string COLLECTION = 'order';
/**
* Cart
*
* Search for the connected cart
*
* @param array &$errors Registry of errors
*
* @return cart|null The connected cart, if found
*/
public function cart(array &$errors = []): ?cart
{
try {
if ($result = collection::execute(
<<<'AQL'
FOR v IN 1..1 INBOUND @order GRAPH users
FILTER PARSE_IDENTIFIER(v._id).collection == @cart
LIMIT 1
return v
AQL,
[
'order' => $this->document->getId(),
'cart' => cart::COLLECTION
],
errors: $errors
)) {
// Found the instance of the cart connected to the order
// Initializing the object
$cart = new cart;
if (method_exists($cart, '__document')) {
// Object can implement a document from ArangoDB
// Writing the instance of the cart document from ArangoDB to the implement object
$cart->__document($result);
// Exit (success)
return $cart;
}
}
} 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;
}
/**
* Account
*
* Search for the connected account
*
* @param array &$errors Registry of errors
*
* @return account|null The connected account, if found
*/
public function account(array &$errors = []): ?account
{
try {
if ($result = collection::execute(
<<<'AQL'
FOR v IN 1..1 OUTBOUND @order GRAPH users
FILTER PARSE_IDENTIFIER(v._id).collection == @account
LIMIT 1
return v
AQL,
[
'order' => $this->document->getId(),
'account' => account::COLLECTION
],
errors: $errors
)) {
// Found the instance of the account connected to the account
// Initializing the object
$account = new account;
if (method_exists($account, '__document')) {
// Object can implement a document from ArangoDB
// Implementinf parameters
if (isset($result->language)) $result->language = language::{$result->language};
if (isset($result->currency)) $result->currency = currency::{$result->currency};
// Writing the instance of the account document from ArangoDB to the implement object
$account->__document($result);
// Exit (success)
return $account;
}
}
} 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

@ -2,15 +2,15 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
namespace mirzaev\huesos\models;
// Files of the project
use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\traits\document as document_trait,
mirzaev\arming_bot\models\interfaces\document as document_interface,
mirzaev\arming_bot\models\interfaces\collection as collection_interface,
mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\enumerations\currency;
use mirzaev\huesos\models\core,
mirzaev\huesos\models\traits\document as document_trait,
mirzaev\huesos\models\interfaces\document as document_interface,
mirzaev\huesos\models\interfaces\collection as collection_interface,
mirzaev\huesos\models\enumerations\language,
mirzaev\huesos\models\enumerations\currency;
// Framework for ArangoDB
use mirzaev\arangodb\collection,
@ -25,7 +25,7 @@ use exception;
/**
* Model of a product
*
* @package mirzaev\arming_bot\models
* @package mirzaev\huesos\models
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
@ -100,7 +100,7 @@ final class product extends core implements document_interface, collection_inter
] + $data,
errors: $errors
);
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
@ -225,7 +225,7 @@ final class product extends core implements document_interface, collection_inter
// Exit (success)
return is_array($result) ? $result : [$result];
}
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
@ -281,7 +281,7 @@ final class product extends core implements document_interface, collection_inter
// Exit (success)
return is_array($result) ? $result : [$result];
} else return [];
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [

View File

@ -2,14 +2,14 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
namespace mirzaev\huesos\models;
// Files of the project
use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\cart,
mirzaev\arming_bot\models\traits\document as document_trait,
mirzaev\arming_bot\models\interfaces\document as document_interface,
mirzaev\arming_bot\models\interfaces\collection as collection_interface;
use mirzaev\huesos\models\core,
mirzaev\huesos\models\cart,
mirzaev\huesos\models\traits\document as document_trait,
mirzaev\huesos\models\interfaces\document as document_interface,
mirzaev\huesos\models\interfaces\collection as collection_interface;
// Framework for ArangoDB
use mirzaev\arangodb\enumerations\collection\type;
@ -18,7 +18,7 @@ use mirzaev\arangodb\enumerations\collection\type;
* Model of reservtion
*
* @used-by cart
* @package mirzaev\arming_bot\models
* @package mirzaev\huesos\models
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>

View File

@ -2,20 +2,20 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
namespace mirzaev\huesos\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\buffer,
mirzaev\arming_bot\models\traits\cart,
mirzaev\arming_bot\models\traits\document as document_trait,
mirzaev\arming_bot\models\interfaces\document as document_interface,
mirzaev\arming_bot\models\interfaces\collection as collection_interface,
mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\enumerations\currency;
use mirzaev\huesos\models\account,
mirzaev\huesos\models\connect,
mirzaev\huesos\models\enumerations\session as verification,
mirzaev\huesos\models\traits\status,
mirzaev\huesos\models\traits\buffer,
mirzaev\huesos\models\traits\cart,
mirzaev\huesos\models\traits\document as document_trait,
mirzaev\huesos\models\interfaces\document as document_interface,
mirzaev\huesos\models\interfaces\collection as collection_interface,
mirzaev\huesos\models\enumerations\language,
mirzaev\huesos\models\enumerations\currency;
// Framework for ArangoDB
use mirzaev\arangodb\collection,
@ -30,7 +30,7 @@ use exception;
/**
* Model of a session
*
* @package mirzaev\arming_bot\models
* @package mirzaev\huesos\models
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
@ -114,14 +114,14 @@ final class session extends core implements document_interface, collection_inter
$session->hash = sodium_bin2hex(sodium_crypto_generichash($_id));
if (document::update($session, errors: $errors)) {
// Writed to ArangoDB
// Writed into ArangoDB
// 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('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
@ -185,9 +185,9 @@ final class session extends core implements document_interface, collection_inter
return $account;
} else throw new exception('Class ' . account::class . ' does not implement a document from ArangoDB');
} else return null;
} 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);
} else throw new exception('Failed to initialize ' . account::TYPE->name . ' collection: ' . account::COLLECTION);
} else throw new exception('Failed to initialize ' . connect::TYPE->name . ' collection: ' . connect::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
@ -232,7 +232,7 @@ final class session extends core implements document_interface, collection_inter
],
errors: $errors
);
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [
@ -279,7 +279,7 @@ final class session extends core implements document_interface, collection_inter
],
errors: $errors
);
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [

View File

@ -2,15 +2,15 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
namespace mirzaev\huesos\models;
// Files of the project
use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\traits\document as document_trait,
mirzaev\arming_bot\models\interfaces\document as document_interface,
mirzaev\arming_bot\models\interfaces\collection as collection_interface,
mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\enumerations\currency;
use mirzaev\huesos\models\core,
mirzaev\huesos\models\traits\document as document_trait,
mirzaev\huesos\models\interfaces\document as document_interface,
mirzaev\huesos\models\interfaces\collection as collection_interface,
mirzaev\huesos\models\enumerations\language,
mirzaev\huesos\models\enumerations\currency;
// Framework for ArangoDB
use mirzaev\arangodb\collection,
@ -25,7 +25,7 @@ use exception;
/**
* Model of settings
*
* @package mirzaev\arming_bot\models
* @package mirzaev\huesos\models
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
@ -96,7 +96,7 @@ final class settings extends core implements document_interface, collection_inte
// Re-search (without creating) and exit (success || fail)
return static::active(errors: $errors);
} else throw new exception('Active settings not found');
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [

View File

@ -2,14 +2,14 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models;
namespace mirzaev\huesos\models;
// Files of the project
use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\traits\document as document_trait,
mirzaev\arming_bot\models\interfaces\document as document_interface,
mirzaev\arming_bot\models\interfaces\collection as collection_interface,
mirzaev\arming_bot\models\enumerations\language;
use mirzaev\huesos\models\core,
mirzaev\huesos\models\traits\document as document_trait,
mirzaev\huesos\models\interfaces\document as document_interface,
mirzaev\huesos\models\interfaces\collection as collection_interface,
mirzaev\huesos\models\enumerations\language;
// Framework for ArangoDB
use mirzaev\arangodb\collection,
@ -25,7 +25,7 @@ use exception,
/**
* Model of a suspension
*
* @package mirzaev\arming_bot\models
* @package mirzaev\huesos\models
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
@ -84,7 +84,7 @@ final class suspension extends core implements document_interface, collection_in
return $suspension;
} else throw new exception('Class ' . static::class . ' does not implement a document from ArangoDB');
} else return null;
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [

File diff suppressed because it is too large Load Diff

View File

@ -2,12 +2,12 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models\traits;
namespace mirzaev\huesos\models\traits;
// Files of the project
use mirzaev\arming_bot\models\interfaces\collection as collection_interface,
mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\enumerations\currency;
use mirzaev\huesos\models\interfaces\collection as collection_interface,
mirzaev\huesos\models\enumerations\language,
mirzaev\huesos\models\enumerations\currency;
// Library for ArangoDB
use ArangoDBClient\Document as _document;
@ -28,7 +28,7 @@ use exception;
* @param static TYPE Type of the collection in ArangoDB
*
* @uses collection_interface
* @package mirzaev\arming_bot\models\traits
* @package mirzaev\huesos\models\traits
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
@ -64,7 +64,7 @@ trait buffer
// 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);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [

View File

@ -2,14 +2,14 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models\traits;
namespace mirzaev\huesos\models\traits;
// Files of the project
use mirzaev\arming_bot\models\interfaces\collection as collection_interface,
mirzaev\arming_bot\models\interfaces\document as document_interface,
mirzaev\arming_bot\models\traits\document as document_trait,
mirzaev\arming_bot\models\connect,
mirzaev\arming_bot\models\cart as model;
use mirzaev\huesos\models\interfaces\collection as collection_interface,
mirzaev\huesos\models\interfaces\document as document_interface,
mirzaev\huesos\models\traits\document as document_trait,
mirzaev\huesos\models\connect,
mirzaev\huesos\models\cart as model;
// Library для ArangoDB
use ArangoDBClient\Document as _document;
@ -28,7 +28,7 @@ use exception;
*
* @uses collection_interface
* @uses document_interface
* @package mirzaev\arming_bot\models\traits
* @package mirzaev\huesos\models\traits
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
@ -132,9 +132,9 @@ trait cart
} else throw new exception('Class ' . model::class . ' does not implement a document from ArangoDB');
} else throw new exception('Failed to create or find just created ' . static::class);
}
} else throw new exception('Failed to initialize ' . model::TYPE . ' collection: ' . model::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);
} else throw new exception('Failed to initialize ' . model::TYPE->name . ' collection: ' . model::COLLECTION);
} else throw new exception('Failed to initialize ' . connect::TYPE->name . ' collection: ' . connect::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [

View File

@ -2,12 +2,12 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models\traits;
namespace mirzaev\huesos\models\traits;
// Files of the project
use mirzaev\arming_bot\models\interfaces\document as document_interface,
mirzaev\arming_bot\models\interfaces\collection as collection_interface,
mirzaev\arming_bot\models\connect;
use mirzaev\huesos\models\interfaces\document as document_interface,
mirzaev\huesos\models\interfaces\collection as collection_interface,
mirzaev\huesos\models\connect;
// Library для ArangoDB
use ArangoDBClient\Document as _document;
@ -26,7 +26,7 @@ use exception;
* @var protected readonly _document|null $document An instance of the ArangoDB document
*
* @uses document_interface
* @package mirzaev\arming_bot\models\traits
* @package mirzaev\huesos\models\traits
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
@ -79,10 +79,12 @@ trait document
/**
* Connect
*
* Searches for a connection document, otherwise creates one
*
* @param collecton_interface $document Document
* @param array &$errors Registry of errors
*
* @return string|null The identifier of the created edge of the "connect" collection, if created
* @return string|null The identifier of the "connect" edge collection, if created or found
*/
public function connect(collection_interface $document, array &$errors = []): ?string
{
@ -95,19 +97,50 @@ trait document
if ($this->document instanceof _document) {
// Initialized instance of the document from ArangoDB
// Writing document and exit (success)
return framework_document::write(
connect::COLLECTION,
// Searching for a connection
$found = collection::execute(
<<<'AQL'
FOR d IN @@collection
FILTER d._from == @_from && d._to == @_to && d.active == true
RETURN d
AQL,
[
'@collection' => connect::COLLECTION,
'_from' => $document->getId(),
'_to' => $this->document->getId()
],
errors: $errors
);
if ($found) {
// Found the connection document
// Exit (success)
return $found->getId();
} else {
// Not found the connection document
// Creting the connection document
$created = framework_document::write(
connect::COLLECTION,
[
'_from' => $document->getId(),
'_to' => $this->document->getId()
],
errors: $errors
);
if ($created) {
// Created the connection document
// Exit (success)
return $created;
}
}
} else throw new exception('The instance of the document from ArangoDB is not initialized');
} else throw new exception('Failed to initialize ' . $document::TYPE . ' collection: ' . $document::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);
} else throw new exception('Failed to initialize ' . $document::TYPE->name . ' collection: ' . $document::COLLECTION);
} else throw new exception('Failed to initialize ' . connect::TYPE->name . ' collection: ' . connect::COLLECTION);
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
} catch (exception $e) {
// Writing to the registry of errors
$errors[] = [

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models\traits;
namespace mirzaev\huesos\models\traits;
// Built-in libraries
use exception;
@ -10,7 +10,7 @@ use exception;
/**
* Trait for initialization of files handlers
*
* @package mirzaev\arming_bot\models\traits
* @package mirzaev\huesos\models\traits
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models\traits;
namespace mirzaev\huesos\models\traits;
// Built-in libraries
use exception;
@ -10,7 +10,7 @@ use exception;
/**
* Trait for initialization of a status
*
* @package mirzaev\arming_bot\models\traits
* @package mirzaev\huesos\models\traits
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace mirzaev\arming_bot\models\traits\yandex;
namespace mirzaev\huesos\models\traits\yandex;
// Framework for PHP
use mirzaev\minimal\http\enumerations\content;
@ -13,7 +13,7 @@ use exception;
/**
* Trait for "Yandex Disk"
*
* @package mirzaev\arming_bot\models\traits\yandex
* @package mirzaev\huesos\models\traits\yandex
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
@ -21,14 +21,14 @@ use exception;
trait disk
{
/**
* Download file from "Yandex Disk"
* Download the file from "Yandex Disk"
*
* @param string $uri URI of the file from "Yandex Disk"
* @param string $destination Destination to write the file
* @param string|int $name Name for the file
* @param array &$errors Registry of errors
*
* @return array|false The [destination, name, content] of the downloaded file, if the file was downloaded
* @return array|false The [destination, name, content] array of the downloaded file, if the file was downloaded
*/
private static function download(
string $uri,
@ -101,4 +101,52 @@ trait disk
// Exit (fail)
return false;
}
/**
* Initialize list of files inside the folder from "Yandex Disk"
*
* @param string $uri URI of the folder from "Yandex Disk"
* @param array &$errors Registry of errors
*
* @return array|false JSON objects list of files in the folder
*/
private static function list(string $uri, array &$errors = []): array|false
{
try {
if (!empty($uri)) {
// Not empty URI
// Initializing URL of the file
$url = "https://cloud-api.yandex.net/v1/disk/public/resources?public_key=$uri";
// Checking if the folder is available
$session = curl_init($url);
curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
curl_exec($session);
$code = curl_getinfo($session, CURLINFO_RESPONSE_CODE);
curl_close($session);
if ($code === 200) {
// The folder is available
// Downloading the list of files in the folder
$files = json_decode(file_get_contents($url));
// Exit (success)
return isset($files?->_embedded?->items) ? $files->_embedded->items : [$files];
} else throw new exception("Failed to download list of files inside the folder by link: $uri");
} 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

@ -2,28 +2,31 @@
declare(strict_types=1);
namespace mirzaev\arming_bot;
namespace mirzaev\huesos;
// Framework for PHP
use mirzaev\minimal\core,
mirzaev\minimal\route;
// Enabling debugging
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');
define('VIEWS', realpath('..' . DIRECTORY_SEPARATOR . 'views'));
define('STORAGE', realpath('..' . DIRECTORY_SEPARATOR . 'storage'));
define('SETTINGS', realpath('..' . DIRECTORY_SEPARATOR . 'settings'));
define('SETTINGS_PROJECT', require(SETTINGS . DIRECTORY_SEPARATOR . 'project.php'));
define('INDEX', __DIR__);
define('ROOT', INDEX . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR);
define('THEME', 'default');
define('CDEK', require(SETTINGS . DIRECTORY_SEPARATOR . 'deliveries' . DIRECTORY_SEPARATOR . 'cdek.php'));
define('TELEGRAM_KEY', require(SETTINGS . DIRECTORY_SEPARATOR . 'telegram.php'));
// Initialize dependencies
require ROOT . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
@ -37,9 +40,12 @@ $core->router
->write('/cart', new route('cart', 'index', 'cart'), 'GET')
->write('/cart/product', new route('cart', 'product', 'cart'), 'PATCH')
->write('/cart/summary', new route('cart', 'summary', 'cart'), 'GET')
/* ->write('/cart/share', new route('cart', 'share', 'cart'), 'POST') */
->write('/cart/share', new route('cart', 'share', 'cart'), 'POST')
->write('/cart/attach', new route('cart', 'attach', 'cart'), 'POST')
->write('/order/robokassa', new route('cart', 'robokassa', 'cart'), 'GET')
->write('/api/robokassa/result', new route('api\acquirings\robokassa', 'result'), 'POST')
->write('/robokassa/success', new route('api\acquirings\robokassa', 'success'), 'GET')
->write('/robokassa/fail', new route('api\acquirings\robokassa', 'fail'), 'GET')
->write('/account/write', new route('account', 'write', 'account'), 'PATCH')
->write('/session/write', new route('session', 'write', 'session'), 'PATCH')
->write('/session/connect/telegram', new route('session', 'telegram', 'session'), 'PUT')

View File

@ -0,0 +1,28 @@
// Initializing the closing element
const closing = document.getElementById("closing");
console.log(closing);
// Initializing the closing iterator
let iterator =
parseInt(closing.style.getPropertyValue("--iterator").replaceAll("'", "")) ||
0;
console.log(iterator);
// Initializing the closing inteval
const interval = setInterval(function () {
if (iterator-- <= 0) {
// Deinitializing the closing inteval
clearInterval(interval);
core.modules.connect("telegram").then(() => {
// Imported the telegram module
// Closing the window
core.telegram.api.close();
});
// Exit (success)
return;
}
// Writing the iterator into the closing element
closing.style.setProperty("--iterator", "'" + iterator + "'");
}, 1000);

View File

@ -17,11 +17,11 @@ class core {
// Window
static window;
// The "loading" element
static status_loading = document.getElementById("loading");
// Account
static account;
// The "account" element
static status_account = document.getElementById("account");
// The "loading" element
static loading = document.getElementById("loading");
// The <header> element
static header = document.body.getElementsByTagName("header")[0];

View File

@ -30,10 +30,10 @@ export default class account {
* @return {void}
*/
static authentication() {
core.status_loading.removeAttribute("disabled");
core.loading.removeAttribute("disabled");
const timer_for_response = setTimeout(() => {
core.status_loading.setAttribute("disabled", true);
core.loading.setAttribute("disabled", true);
}, 200);
core.modules.connect("telegram").then(() => {
@ -44,7 +44,7 @@ export default class account {
.request(
"/session/connect/telegram",
core.telegram.api.initData,
'PUT'
"PUT",
)
.then((json) => {
if (json) {
@ -63,20 +63,66 @@ export default class account {
// Success (not received errors)
if (json.connected === true) {
core.status_loading.setAttribute("disabled", true);
// Deactivating the loading screen
core.loading.setAttribute("disabled", true);
clearTimeout(timer_for_response);
const a = core.status_account.getElementsByTagName("a")[0];
a.setAttribute("onclick", "core.account.profile()");
a.innerText = json.domain.length > 0
? "@" + json.domain
: "ERROR";
core.account = {
identifier: json.identifier
};
// Initializing the account element
const account = document.getElementById("account");
if (account instanceof HTMLElement) {
// Initialized the account element
// Initializing the account link
const a = account.getElementsByTagName("a")[0];
if (a instanceof HTMLElement) {
// Initialized the account link
a.setAttribute("onclick", "core.account.profile()");
a.innerText = json.domain.length > 0
? "@" + json.domain
: "ERROR";
} else {
// Not initialized the account link
if (json.avatar) {
// Received the avatar image
// Initialize the menu button icon
const icon = account.getElementsByTagName("i")[0];
if (icon instanceof HTMLElement) {
// Initialized the menu button icon
setTimeout(function () {
// Hiding the menu button icon
icon.classList.add("hidden");
}, 3000);
}
// Initializing the avatar image element
const image = document.createElement("img");
image.setAttribute("src", json.avatar);
image.style.setProperty("opacity", "0");
image.style.setProperty("--animation-delay", "2s");
image.classList.add("opacity", "animated");
// Writing the avatar image element
account.appendChild(image);
}
}
}
}
if (
json.language !== null &&
typeof json.language === "string" &&
json.langiage.length === 2
json.language.length === 2
) {
core.language = json.language;
}

View File

@ -69,7 +69,7 @@ export default class cart {
* @name Write (interface)
*
* @description
* Write the product in the cart
* Write the product into the cart
*
* @param {HTMLButtonElement|HTMLInputElement|null} element Handler elememnt of the product
* @param {HTMLElement} product The product element
@ -388,7 +388,7 @@ export default class cart {
static async share(button) {
return await core.modules.connect("telegram").then(
() => {
// Imported the damper module
// Imported the telegram module
// Disabling button
button?.setAttribute("disabled", true);
@ -771,6 +771,24 @@ Object.assign(
} else {
// Success (not received errors)
if (json.amount > 0) {
// The cart has products
// Writing the CSS variable of the document element
document.documentElement.style.setProperty(
"--cart-amount",
'"' + json.amount + '"',
);
} else {
// The cart has no products
// Writing the CSS variable of the document element
document.documentElement.style.setProperty(
"--cart-amount",
"unset",
);
}
// Initializing the summary amount <span> element
const amount = document.getElementById("amount");
@ -844,7 +862,7 @@ Object.assign(
try {
// Request
return await core.request("/cart/share", undefined, "POST")
.then((json) => {
.then(async (json) => {
if (json) {
// Received a JSON-response
@ -863,13 +881,43 @@ Object.assign(
if (json.share) {
// Received sharing hash
// Request to the chat-robot
core.telegram.api.sendData(
// Sending the request to the chat-robot
const sended = core.telegram.api.sendData(
JSON.stringify({
type: "cart_share",
hash: json.share,
}),
);
if (sended === undefined) {
// Failed to send the request
if (core.account.identifier > 0) {
// Initialized the account identifier
// Request
return await core.request(
"/cart/attach",
'share=' + json.share,
"POST",
).then((json) => {
if (json) {
// Received a JSON-response
if (json.success) {
// Received the success status
core.modules.connect("telegram").then(() => {
// Imported the telegram module
// Closing the Telegram Mini App
core.telegram.api.close();
});
}
}
});
}
}
}
/* if (json.robokassa) {

View File

@ -15,6 +15,11 @@ export default class loader {
*/
static type = "module";
/**
* @name Actial URI
*/
static uri;
/**
* @name Load
*
@ -23,7 +28,7 @@ export default class loader {
*
* @return {Promise}
*/
static async load(uri = "/", body) {
static async load(uri = "/", body, back = false) {
if (typeof uri === "string") {
// Received and validated uri
@ -54,8 +59,27 @@ export default class loader {
} else {
// Success (not received errors)
// Writing to the browser history
history.pushState({}, json.title ?? uri, uri);
if (back) {
// Requested to go back
// Writing actual URI
this.uri = history.state?.previous;
// Deletimg from the browser history
history.back();
} else {
// Requested to go forward
// Writing to the browser history
history.pushState(
{ previous: this.uri },
json.title ?? uri,
uri,
);
// Writing actual URI
this.uri = uri;
}
/**
* The <title>
@ -222,7 +246,10 @@ export default class loader {
// Found the last <section> element inside the <body> element
// Writing the <main> element after the last <section> element inside the <body> element
body.insertBefore(core.main, section.nextElementSibling);
body.insertBefore(
core.main,
section.nextElementSibling,
);
} else {
// Not found section elements <section> inside the <body> element

View File

@ -21,6 +21,11 @@ export default class telegram {
* @see {@link https://core.telegram.org/bots/webapps#initializing-mini-apps}
*/
static api = window.Telegram.WebApp;
/**
* @name List of BackButton events
*/
static back = [];
}
// Connecting to the core

View File

@ -2,10 +2,10 @@
core.modules.connect(["telegram"])
.then(() => {
//
// Imported the telegram module
// Expanding the "Web App" window
// core.telegram.api.expand();
core.telegram.api.expand();
// Writing settings for the "Web App" window
core.telegram.api.enableVerticalSwipes();
@ -24,6 +24,20 @@ core.modules.connect(["telegram"])
document.documentElement.style.setProperty('--tg-theme-section-bg-color', 'var(--tg-theme-secondary-bg-color)');
}
if (core.telegram.back.length > 0) {
// Initialized BackButton events
// Initializing the "Back Button" of the "Web App" window
core.telegram.api.BackButton.show();
for (const event of core.telegram.back) {
// Iterating over BackButton events
// Initializing the BackButton event listener
core.telegram.api.BackButton.onClick(event);
}
}
core.modules.connect(["session", "account"])
.then(() => {
//

View File

@ -0,0 +1,239 @@
<?php
declare(strict_types=1);
namespace mirzaev\huesos;
// Files of the project
use mirzaev\huesos\controllers\core as controller,
mirzaev\huesos\models\core as model,
mirzaev\huesos\models\cart,
mirzaev\huesos\models\telegram;
// Framework for Telegram
use Zanzara\Zanzara as zanzara,
Zanzara\Context as context,
Zanzara\Config as config;
// Framework for ArangoDB
use mirzaev\arangodb\document;
ini_set('error_reporting', E_ALL ^ E_DEPRECATED);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
// Версия робота
define('ROBOT_VERSION', '1.0.0');
// Путь до настроек
define('SETTINGS', __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'settings');
// Путь до хранилища
define('STORAGE', __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'storage');
// Файл в формате xlsx с примером excel-документа для импорта каталога
define('CATALOG_EXAMPLE', STORAGE . DIRECTORY_SEPARATOR . 'example.xlsx');
// Файл
define('GREETING_VIDEO', STORAGE . DIRECTORY_SEPARATOR . 'greeting.mp4');
// Файл в формате xlsx для импорта каталога
define('CATALOG_IMPORT', STORAGE . DIRECTORY_SEPARATOR . 'import.xlsx');
/**
* Ключ чат-робота Telegram
* @deprecated
*/
define('KEY', require(SETTINGS . DIRECTORY_SEPARATOR . 'telegram.php'));
define('TELEGRAM_KEY', require(SETTINGS . DIRECTORY_SEPARATOR . 'telegram.php'));
// Initialize dependencies
require __DIR__ . DIRECTORY_SEPARATOR
. '..' . DIRECTORY_SEPARATOR
. '..' . DIRECTORY_SEPARATOR
. '..' . DIRECTORY_SEPARATOR
. '..' . DIRECTORY_SEPARATOR
. 'vendor' . DIRECTORY_SEPARATOR
. 'autoload.php';
// Инициализация ядра контроллеров MINIMAL
/* new controller(new core, false); */
// Инициализация ядра моделей MINIMAL
new model(true);
$config = new config();
$config->setParseMode(config::PARSE_MODE_MARKDOWN);
$config->useReactFileSystem(true);
$robot = new zanzara(TELEGRAM_KEY, $config);
$robot->onUpdate(function (context $context): void {
// Initializing the message
$message = $context->getMessage();
// Initializing the "web app" data
$app = $message?->getWebAppData();
if (!empty($app)) {
// Initialized the "web app" data
// Initializing request from "web app" data
$request = json_decode($app->getData(), false, 10);
if ($request->type === 'cart_share') {
// Cart attaching
// Attaching cart to the Telegram account
telegram::cart_attach($context, $request->hash);
}
} else {
// Not initialized the "web app" data
// Initializing account
$account = $context->get('account');
if ($account) {
// Initialized the account
if (!empty($message)) {
// Initialized the message
// Initializing the contact data
$contact = $message?->getContact();
if (!empty($contact)) {
// Initialized the contact data
// Sanitizing received SIM-number (only numbers)
$sanitized = preg_replace('/[^\d]/', '', $contact->getPhoneNumber());
if (!empty($sanitized)) {
// Sanitized receiver SIM-number
// Writing receiver SIM-number into the account
$account->receiver = ['sim' => (int) $sanitized] + ($account->receiver ?? []);
// Deabstracting the language parameter
$account->language = $account->language->name;
if (document::update($account->__document())) {
// Writed the account instance into the ArangoDB document
/*
// Sending the message
$context->sendMessage(
<<<TXT
*SIM\-номер зарегистрирован:* $sanitized
TXT,
[
'reply_markup' => [
'remove_keyboard' => true
]
]
)->then(function ($message) use ($context) {
// Sended message
// Sending the account parameters menu
telegram::account_parameters($context);
}); */
// Sending the message
$context->sendMessage(
<<<TXT
*Номер телефона зарегистрирован:* $sanitized
TXT,
[
'reply_markup' => [
'remove_keyboard' => true
]
]
)->then(function ($message) use ($context) {
// Sended message
// Sending the account parameters menu
telegram::account_parameters($context);
});
} else {
// Not writed the account instance into the ArangoDB document
// Sending the message
$context->sendMessage('⚠️ *Не удалось записать SIM\-номер*');
}
}
}
}
}
}
unset($app);
});
$robot->onCommand('start', fn(context $context) => telegram::start($context));
$robot->onCommand('contacts', fn(context $context) => telegram::contacts($context));
$robot->onCommand('company', fn(context $context) => telegram::company($context));
$robot->onCommand('community', fn(context $context) => telegram::community($context));
$robot->onCommand('settings', fn(context $context) => telegram::settings($context));
$robot->onText('💬 Контакты', fn(context $context) => telegram::contacts($context));
$robot->onText('🏛️ О компании', fn(context $context) => telegram::company($context));
$robot->onText('🎯 Сообщество', fn(context $context) => telegram::community($context));
$robot->onText('⚙️ Настройки', fn(context $context) => telegram::settings($context));
$robot->onCbQueryData(['contacts'], fn(context $context) => telegram::contacts($context));
$robot->onCbQueryData(['company'], fn(context $context) => telegram::company($context));
$robot->onCbQueryData(['community'], fn(context $context) => telegram::community($context));
$robot->onCbQueryData(['settings'], fn(context $context) => telegram::settings($context));
$robot->onCbQueryData(['mail'], fn(context $context) => telegram::_mail($context));
$robot->onCbQueryData(['import_request'], fn(context $context) => telegram::import_request($context));
$robot->onCbQueryData(['order_commentary_request'], fn(context $context) => telegram::order_commentary_request($context));
$robot->onCbQueryData(['order'], fn(context $context) => telegram::order($context));
$robot->onCbQueryData(['tuning'], fn(context $context) => telegram::tuning($context));
$robot->onCbQueryData(['brands'], fn(context $context) => telegram::brands($context));
$robot->onCbQueryData(['cart_delivery'], fn(context $context) => telegram::cart_delivery($context));
$robot->onCbQueryData(['delivery_registration_destination_office'], fn(context $context) => telegram::delivery_registration_destination_office($context));
$robot->onCbQueryData(['delivery_registration_destination_door'], fn(context $context) => telegram::delivery_registration_destination_door($context));
$robot->onCbQueryData(['delivery_registration_sim_input'], fn(context $context) => telegram::delivery_registration_sim_input($context));
$robot->onCbQueryData(['delivery_registration_name_input'], fn(context $context) => telegram::delivery_registration_name_input($context));
$robot->onCbQueryData(['delivery_registration_address_input'], fn(context $context) => telegram::delivery_registration_address_input($context));
$robot->onCbQueryData(['delivery_registration'], fn(context $context) => telegram::delivery_registration($context));
$robot->onCbQueryData(['account_parameters_force'], fn(context $context) => telegram::account_parameters($context, force: true));
$robot->onCbQueryData(['receiver_sim_choose'], fn(context $context) => telegram::receiver_sim_choose($context));
$robot->onCbQueryData(['receiver_sim_request'], fn(context $context) => telegram::receiver_sim_request($context));
/* $robot->onCbQueryData(['receiver_sim_input'], fn(context $context) => telegram::receiver_sim_input($context)); */
$robot->onCbQueryData(['receiver_sim_write'], fn(context $context) => telegram::receiver_sim_write($context));
$robot->onCbQueryData(['receiver_name_choose'], fn(context $context) => telegram::receiver_name_choose($context));
$robot->onCbQueryData(['receiver_name_request'], fn(context $context) => telegram::receiver_name_request($context));
/* $robot->onCbQueryData(['receiver_name_input'], fn(context $context) => telegram::receiver_name_input($context)); */
$robot->onCbQueryData(['receiver_name_write'], fn(context $context) => telegram::receiver_name_write($context));
$robot->onCbQueryData(['receiver_destination_choose'], fn(context $context) => telegram::receiver_destination_choose($context));
$robot->onCbQueryData(['receiver_destination_office'], fn(context $context) => telegram::receiver_destination_office($context));
$robot->onCbQueryData(['receiver_destination_door'], fn(context $context) => telegram::receiver_destination_door($context));
$robot->onCbQueryData(['receiver_address_choose'], fn(context $context) => telegram::receiver_address_choose($context));
$robot->onCbQueryData(['receiver_address_request_office'], fn(context $context) => telegram::receiver_address_request($context, office: true));
$robot->onCbQueryData(['receiver_address_request_door'], fn(context $context) => telegram::receiver_address_request($context, office: false));
/* $robot->onCbQueryData(['receiver_address_input'], fn(context $context) => telegram::receiver_address_input($context)); */
$robot->onCbQueryData(['receiver_address_write'], fn(context $context) => telegram::receiver_address_write($context));
$robot->onException(function (Context $context, $exception) {
var_dump($exception);
});
// Инициализация middleware с обработкой аккаунта
$robot->middleware([telegram::class, "account"]);
// Инициализация middleware с обработкой технических работ разных уровней
$robot->middleware([telegram::class, "suspension"]);
// Запуск чат-робота
$robot->run();

View File

@ -2,12 +2,12 @@
declare(strict_types=1);
namespace mirzaev\arming_bot;
namespace mirzaev\huesos;
// Files of the project
use mirzaev\arming_bot\controllers\core as controller,
mirzaev\arming_bot\models\core as model,
mirzaev\arming_bot\models\socket;
use mirzaev\huesos\controllers\core as controller,
mirzaev\huesos\models\core as model,
mirzaev\huesos\models\socket;
// Framework for PHP
use mirzaev\minimal\core;

View File

@ -0,0 +1,19 @@
@charset "UTF-8";
section#account {
z-index: 999;
position: fixed;
bottom: 20px;
left: 20px;
height: 16px;
display: flex;
}
nav#menu>a#account>img {
position: absolute;
width: auto;
height: inherit;
object-fit: contain;
border-radius: 100%;
/* border: 2px solid var(--tg-theme-bottom-bar-bg-color); */
}

View File

@ -0,0 +1,40 @@
@charset "UTF-8";
main {
--offset-bottom: 2rem;
justify-content: center;
gap: unset;
}
main>:is(:first-child, :last-child) {
margin-top: auto;
}
main>h2 {
margin: unset;
margin-bottom: 0.5rem;
font-size: 1.2rem;
color: var(--tg-theme-accent-text-color);
}
main>h2+small {
margin-bottom: calc(0 - var(--offset-bottom));
color: var(--tg-theme-subtitle-text-color);
}
main>#closing {
display: flex;
align-items: center;
font-size: 0.8rem;
margin-bottom: var(--offset-bottom);
}
main>#closing>i.icon:first-child {
margin-right: 1rem;
}
main>#closing:after {
content: ': ' var(--iterator);
font-weight: bold;
color: var(--tg-theme-accent-text-color);
}

View File

@ -0,0 +1,40 @@
@charset "UTF-8";
main {
--offset-bottom: 2rem;
justify-content: center;
gap: unset;
}
main>:is(:first-child, :last-child) {
margin-top: auto;
}
main>h2 {
margin: unset;
margin-bottom: 0.5rem;
font-size: 1.2rem;
color: var(--tg-theme-accent-text-color);
}
main>h2+small {
margin-bottom: calc(0 - var(--offset-bottom));
color: var(--tg-theme-subtitle-text-color);
}
main>#closing {
display: flex;
align-items: center;
font-size: 0.8rem;
margin-bottom: var(--offset-bottom);
}
main>#closing>i.icon:first-child {
margin-right: 1rem;
}
main>#closing:after {
content: ': ' var(--iterator);
font-weight: bold;
color: var(--tg-theme-accent-text-color);
}

View File

@ -0,0 +1,57 @@
@charset "UTF-8";
@keyframes slide-down {
0% {
transform: translate(0, -100%);
clip-path: polygon(0% 100%, 100% 100%, 100% 200%, 0% 200%);
}
100% {
transform: translate(0, 0%);
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
}
}
.slide.down.animated {
animation-duration: var(--animation-duration, 0.25s);
animation-name: slide-down;
animation-fill-mode: forwards;
animation-timing-function: var(--animatiom-timing, ease-out);
}
@keyframes slide-down-revert {
0% {
transform: translate(0, 0%);
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
}
100% {
transform: translate(0, -100%);
clip-path: polygon(0% 100%, 100% 100%, 100% 200%, 0% 200%);
}
}
.slide.down.revert.animated {
animation-duration: var(--animation-duration, 0.25s);
animation-name: slide-down-revert;
animation-fill-mode: forwards;
animation-timing-function: var(--animatiom-timing, ease-in);
}
@keyframes opacity {
0% {
opacity: 0%;
}
100% {
opacity: 100%;
}
}
.opacity.animated {
animation-duration: var(--animation-duration, 0.25s);
animation-name: opacity;
animation-fill-mode: forwards;
animation-timing-function: var(--animatiom-timing, ease-in);
animation-delay: var(--animation-delay, 0s);
}

View File

@ -7,6 +7,10 @@ main>section:is(#summary, #products, #delivery, #delivery_request) {
overflow: hidden;
}
main:not(:has(h2#title + section#delivery))>h2#title {
margin-bottom: 1rem;
}
main>section:is(#summary, #delivery) {
background-color: var(--tg-theme-section-bg-color);
}
@ -245,12 +249,13 @@ main>section#products>article.product>a {
}
main>section#products>article.product>a>img:first-of-type {
/* width: 5rem; */
min-width: 5rem;
width: 7rem;
/* min-width: 5rem; */
min-height: 100%;
object-fit: cover;
image-rendering: auto;
border-radius: 0.75rem;
border: 0.2rem solid var(--tg-theme-hint-color);
box-shadow: -5px 0px 50px 10px rgba(0, 0, 0, 0.4);
-webkit-box-shadow: -5px 0px 50px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: -5px 0px 50px 10px rgba(0, 0, 0, 0.4);
@ -276,12 +281,17 @@ main>section#products>article.product>div>div.head {
gap: 1rem;
}
main>section#products>article.product>div>div.head>button {
align-self: start;
}
main>section#products>article.product>div>div>button:first-of-type {
margin-left: auto;
}
main>section#products>article.product>div>div>button {
padding: 0.4rem;
color: var(--tg-theme-text-color);
background: unset;
}

View File

@ -0,0 +1,346 @@
@charset "UTF-8";
main>section#categories:has(a),
main>section#categories:not(:has(a))>ul:has(li.category[type="button"]) {
width: var(--width);
display: flex;
flex-flow: var(--catalog-categories-wrap-flex-wrap, row wrap);
flex-direction: var(--catalog-categories-wrap-flex-direction, 'row');
}
main>section#categories:has(a) {
gap: var(--gap, 5px);
}
main>section#categories:not(:has(a))>ul:has(li.category[type="button"])>ul {
margin-bottom: var(--gap, 5px);
}
main>section#categories ul {
padding-left: 1.3rem;
list-style-type: none;
overflow: hidden;
width: 100%;
}
main>section#categories>li {
line-height: 1rem;
}
main>section#categories li {
/* font-family: "Chalet"; */
}
main>section#categories li.openable {
font-weight: 400;
}
main>section#categories ul>li:has(+ ul) {
z-index: 100;
}
main>section#categories ul>ul {
--offset: 1rem;
--padding: 1rem;
margin-top: calc(var(--offset, 1rem) * -1);
padding-top: calc(var(--offset, 1rem) + var(--padding, 1rem));
padding-bottom: var(--padding, 1rem);
border-radius: 0 0 0.75rem 0.75rem;
display: flex;
flex-direction: column;
gap: 0.3rem;
background: var(--catalog-categories-buttons-lists-background-color, var(--tg-theme-secondary-bg-color));
}
main>section#categories:not(:has(a))>ul:has(li.category[type="button"]) {
margin: unset;
padding: unset;
}
main>section#categories>a.category[type="button"],
main>section#categories>ul>li.category[type="button"] {
--padding: 0.7rem;
z-index: unset;
position: relative;
height: 23px;
padding: var(--padding);
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: var(--catalog-categories-buttons-background);
}
main>section#categories>a.category[type="button"]:not(.column):after,
main>section#categories>ul>li.category[type="button"]:not(.column):after {
z-index: -100;
position: absoulute;
left: 0;
top: 0;
content: '';
width: 100%;
height: 100%;
filter: brightness(0.8);
background-color: var(--tg-theme-button-color);
}
main>section#categories:last-child {
/* margin-bottom: unset; */
}
/* main>section#categories>a.category[type="button"]:has(>img) {
min-width: calc(50% - var(--padding) * 2);
height: 180px;
padding: unset;
} */
main>section#categories>a.category[type="button"],
main>section#categories>ul>li.category[type="button"] {
width: var(--catalog-categories-buttons-width, calc(50% - var(--padding) * 2));
height: var(--catalog-categories-buttons-height, 180px);
padding: unset;
}
main>section#categories ul>li[type="button"].opened+ul {
max-height: var(--catalog-categories-buttons-lists-height, 500px);
transition: max-height 0.15s ease-in 0.1s, margin 0s ease-out 0.1s, padding 0s ease-out 0.1s;
}
main>section#categories ul>li[type="button"]:not(.opened)+ul {
max-height: 0;
padding-top: 0;
padding-bottom: 0;
margin-top: 0;
transition: max-height 0.25s ease-out, margin 0s ease-out 0.25s, padding 0s ease-out 0.25s;
}
main>section#categories>a.category[type="button"]>img,
main>section#categories>ul>li.category[type="button"]>img {
position: absolute;
left: -5%;
top: -5%;
width: 110%;
height: 110%;
object-fit: cover;
/* filter: blur(1px); */
filter: var(--catalog-categories-buttons-images-filter, contrast(1.2) brightness(60%));
}
main>section#categories>a.category[type="button"]>img.right,
main>section#categories>ul>li.category[type="button"]>img.right {
/* position: absolute; */
left: unset;
top: unset;
right: 0;
width: calc(100% - var(--catalog-categories-buttons-texts-width, 10rem));
}
main>section#categories>a.category[type="button"]:is(:hover, :focus)>img,
main>section#categories>ul>li.category[type="button"]:is(:hover, :focus)>img {
filter: unset;
}
/* main>section#categories>a.category[type="button"]:has(>img)>p { */
main>section#categories>a.category[type="button"]>p,
main>section#categories>ul>li.category[type="button"]>p {
position: absolute;
left: var(--padding);
top: var(--catalog-categories-buttons-texts-top, var(--padding));
bottom: var(--catalog-categories-buttons-texts-bottom, var(--padding));
right: var(--padding);
margin: unset;
width: calc(var(--catalog-categories-buttons-texts-width, 10rem) - 0.7rem * 2);
border-radius: 0.75rem;
color: var(--catalog-categories-buttons-texts-title-color, var(--tg-theme-text-color));
/* background: var(--tg-theme-hint-color); */
}
main>section#categories>a.category[type="button"]>div.separator.gradient:has(+img.right),
main>section#categories>ul>li.category[type="button"]>div.separator.gradient:has(+img.right) {
--background: var(--catalog-categories-buttons-background, var(--tg-theme-button-color));
z-index: 100;
content: '';
position: absolute;
right: 0;
width: calc(100% - var(--catalog-categories-buttons-texts-width, 10rem));
height: 100%;
background: var(--background);
background: linear-gradient(90deg, var(--background) 0%, transparent var(--catalog-categories-buttons-separator-width, 100%));
}
main>section#categories>a.category[type="button"]>p,
main>section#categories>ul>li.category[type="button"]>p {
z-index: 100;
padding: 0 calc(var(--padding) / 2);
}
main>section#categories>a.category[type="button"]>p:not(.background),
main>section#categories>ul>li.category[type="button"]>p:not(.background) {
font-family: "Cygre";
font-weight: 600;
}
main>section#categories>a.category[type="button"]>p.background,
main>section#categories>ul>li.category[type="button"]>p.background {
padding: 8px calc(var(--padding, 0.7rem) * 1.3);
width: min-content;
}
main>section#categories>a.category[type="button"]>p.background:after,
main>section#categories>ul>li.category[type="button"]>p.background:after {
z-index: -100;
position: absolute;
left: 0;
top: 0;
content: '';
width: 100%;
height: 100%;
border-radius: 0.75rem;
background-color: var(--tg-theme-button-color);
box-shadow: 0px 0px 8px 4px rgba(0, 0, 0, 0.1);
-webkit-box-shadow: 0px 0px 8px 4px rgba(0, 0, 0, 0.1);
-moz-box-shadow: 0px 0px 8px 4px rgba(0, 0, 0, 0.1);
}
main>section#filters {
--filters-height: 2rem;
width: var(--width);
max-height: var(--filters-height);
display: flex;
align-items: start;
}
main>section#products {
--column: calc((100% - var(--gap)) / 2);
width: var(--width);
display: grid;
grid-gap: var(--gap);
grid-template-columns: repeat(2, var(--column));
grid-auto-flow: row dense;
}
main>section#products>div.column {
width: 100%;
display: flex;
flex-direction: column;
gap: var(--gap);
}
main>section#products>div.column>article.product {
position: relative;
width: 100%;
display: flex;
flex-grow: 0;
flex-direction: column;
border-radius: 0.75rem;
overflow: clip;
cursor: pointer;
backdrop-filter: brightness(0.7);
background-color: var(--tg-theme-section-bg-color);
}
main>section#products>div.column>article.product:is(:hover, :focus) {
/* flex-grow: 0.1; */
/* background-color: var(--tg-theme-section-bg-color); */
}
main>section#products>div.column>article.product:is(:hover, :focus)>* {
transition: 0s;
}
main>section#products>div.column>article.product:not(:is(:hover, :focus))>* {
transition: 0.2s ease-out;
}
main>section#products>div.column>article.product>a>img:first-of-type {
--border: 0.2rem;
width: calc(100% - var(--border) * 2);
image-rendering: auto;
border-radius: 0.75rem;
border: 0.2rem solid var(--tg-theme-hint-color);
box-shadow: 0px 5px 70px 20px rgba(0, 0, 0, 0.3);
-webkit-box-shadow: 0px 5px 70px 20px rgba(0, 0, 0, 0.3);
-moz-box-shadow: 0px 5px 70px 20px rgba(0, 0, 0, 0.3);
}
main>section#products>div.column>article.product>a>img:first-of-type+* {
margin-top: auto;
}
main>section#products>div.column>article.product>a>p.title {
z-index: 50;
margin: unset;
padding: 4px 8px 8px;
font-size: 0.9rem;
font-weight: bold;
overflow-wrap: anywhere;
hyphens: auto;
color: var(--tg-theme-text-color);
}
main>section#products>div.column>article.product>a>p.title>span {
margin-top: 0.2rem;
display: block;
font-weight: 400;
font-size: small;
color: var(--tg-theme-hint-color);
}
main>section#products>div.column>article.product>div[data-product="buttons"]:last-of-type {
z-index: 100;
height: 33px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
border-radius: 0.75rem;
}
/* main>section#products>div.column>article.product[data-product-amount]:not(:is([data-product-amount="0"], [data-product-amount="1"]))>div[data-product="buttons"]:last-of-type { */
main>section#products>div.column>article.product[data-product-amount]:not(:is([data-product-amount="0"]))>div[data-product="buttons"]:last-of-type {
container-type: inline-size;
container-name: product-buttons;
}
main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"] {
padding: 0;
flex-grow: 1;
background-color: var(--catalog-button-cart-background, var(--tg-theme-button-color));
}
/* main>section#products>div.column>article.product:is([data-product-amount="0"], [data-product-amount="1"])>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-parameter="amount"], */
main>section#products>div.column>article.product:is([data-product-amount="0"])>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-parameter="amount"],
main>section#products>div.column>article.product[data-product-amount="0"]>div[data-product="buttons"]>button:is([data-product-button="write"], [data-product-button="delete"]) {
display: none;
}
main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-parameter="amount"]:after {
content: '*';
margin: 0 0.2rem;
}
main>section#products>div.column>article.product[data-product-amount]:not([data-product-amount="0"])>div[data-product="buttons"]>button[data-product-button="toggle"] {
background-color: var(--catalog-button-cart-added-background, var(--tg-theme-button-color));
}
main>section#products>div.column>article.product[data-product-amount]:not([data-product-amount="0"])>div[data-product="buttons"] {
/* hehe */
filter: var(--catalog-button-cart-added-background, hue-rotate(calc(120deg + var(--hue-rotate-offset, 0deg))));
}
main>section#products>div.column>article.product>div[data-product="buttons"]>button {
background-color: var(--catalog-button-cart-added-background, var(--tg-theme-button-color));
}
@container product-buttons (max-width: 200px) {
main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span:is([data-product-parameter="cost"], [data-product-parameter="currency"]) {
display: none;
}
main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-parameter="amount"]:after {
content: unset;
}
}

View File

@ -0,0 +1,51 @@
@charset "UTF-8";
main>section#categories ul>ul {
--padding: 1rem;
padding: 0;
}
main>section#categories>ul ul>li {
--padding-row: calc(var(--gap, 5px) * 1);
position: relative;
padding: var(--padding-row);
display: flex;
flex-flow: row;
align-items: center;
}
main>section#categories>ul ul>li>:is(small, i.icon):last-child {
margin-left: auto;
margin-right: 0.1rem;
rotate: 0deg;
transition: rotate 0.1s ease-out;
font-family: monospace;
font-weight: bold;
}
main>section#categories>ul ul>li.opened>:is(small, i.icon):last-child {
rotate: 90deg;
transition: rotate 0.1s ease-in;
}
main>section#categories>ul ul>li:after,
main>section#categories>ul ul>li[type="button"].opened+ul:has(+ li)>li:last-of-type:after {
position: absolute;
content: '';
bottom: 0px;
width: calc(100% - var(--padding-row) * 2);
display: block;
align-self: center;
border-bottom: 1px solid var(--catalog-categories-buttons-lists-separatpr-color, var(--tg-theme-hint-color));
transition: bottom 0s linear 0s;
}
main>section#categories>ul ul>li:last-of-type:not(.opened):after {
bottom: -1rem;
transition: bottom 0.1s ease-out 0.25s;
}
main>section#categories>ul ul>li:last-of-type.opened:after {
bottom: -1px;
transition: bottom 0.1s ease-in;
}

View File

@ -0,0 +1,21 @@
@charset "UTF-8";
main>section#categories ul>li:not(.opened):has(+ ul) {
margin-bottom: 0;
transition: margin 0.1s ease-out 0.25s;
}
main>section#categories>ul>li.opened:has(+ ul) {
margin-bottom: var(--gap, 5px);
transition: margin 0.1s ease-in;
}
main>section#categories ul>ul {
--offset: 0rem;
border-radius: 0.75rem;
}
main>section#categories>ul ul>ul {
margin-left: var(--gap, 5px);
width: calc(100% - var(--gap, 5px));
}

View File

@ -0,0 +1,8 @@
@charset "UTF-8";
@font-face {
font-family: 'Chalet';
src: url("/themes/default/fonts/chalet/chalet.otf") format('opentype');
font-weight: 600;
font-style: bold;
}

View File

@ -0,0 +1,8 @@
@charset "UTF-8";
@font-face {
font-family: 'Cygre';
src: url("/themes/default/fonts/cygre/Cygre-Bold.ttf");
font-weight: 600;
font-style: bold;
}

View File

@ -26,7 +26,7 @@ i.icon.arrow:not(.circle, .square)::after {
bottom: 7px;
}
i.icon.arrow:not(.circle, .square)::before {
i.icon.arrow:not(.circle, .square, .short)::before {
width: 16px;
height: 2px;
bottom: 10px;

Some files were not shown because too many files have changed in this diff Show More