Compare commits
No commits in common. "stable" and "1.12.0" have entirely different histories.
|
@ -1,2 +1 @@
|
|||
node_modules
|
||||
vendor
|
||||
|
|
393
README.md
393
README.md
|
@ -1,160 +1,42 @@
|
|||
# huesos
|
||||
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>
|
||||
Basis for developing chat-robots with "Web App" technology for Telegram
|
||||
|
||||
## Installation
|
||||
|
||||
### AnangoDB
|
||||
|
||||
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>
|
||||
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:** category, product<br>
|
||||
1. Create a Graph with the specified values
|
||||
**Name:** catalog<br/>
|
||||
<br/>
|
||||
**edgeDefinition:** entry<br/>
|
||||
**fromCollections:** categoy, product<br/>
|
||||
**toCollections:** category
|
||||
|
||||
* Relation 2<br>
|
||||
**edgeDefinition:** reservation<br>
|
||||
**fromCollections:** product<br>
|
||||
**toCollections:** cart
|
||||
|
||||
---
|
||||
|
||||
3. **Create a Graph with the specified values**<br>
|
||||
**Name:** users<br>
|
||||
|
||||
* Relation 1<br>
|
||||
**edgeDefinition:** connect<br>
|
||||
**fromCollections:** cart, session<br>
|
||||
**toCollections:** account, session<br>
|
||||
|
||||
* Orphan Collections<br>
|
||||
product
|
||||
|
||||
---
|
||||
|
||||
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>
|
||||
|
||||
*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***<br>
|
||||
|
||||
---
|
||||
|
||||
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>
|
||||
|
||||
You can copy an example of view file from here: `/examples/arangodb/views/products_search.json`
|
||||
2. Create a Graph with the specified values
|
||||
**Name:** sessions<br/>
|
||||
<br/>
|
||||
**edgeDefinition:** connect<br/>
|
||||
**fromCollections:** account<br/>
|
||||
**toCollections:** session
|
||||
|
||||
3. Create indexes for the "product" collection
|
||||
**Type:** "Inverted Index"<br/>
|
||||
**Fields:** name.ru<br/>
|
||||
**Analyzer:** "text_ru"<br/>
|
||||
**Search field:** true<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***
|
||||
|
||||
4. Create a View with the specified values
|
||||
**type:** search-alias (you can also use "arangosearch")<br/>
|
||||
**name:** **product**s_search<br/>
|
||||
**indexes:**
|
||||
```json
|
||||
"indexes": [
|
||||
{
|
||||
|
@ -166,189 +48,70 @@ You can copy an example of view file from here: `/examples/arangodb/views/produc
|
|||
|
||||
### NGINX
|
||||
|
||||
1. **Create a NGINX server**<br>
|
||||
You can copy an example of server file from here: `/examples/nginx/server.conf`
|
||||
1. Example of NGINX server file
|
||||
```nginx
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php;
|
||||
}
|
||||
|
||||
2. **Add support for javascript modules**<br>
|
||||
Edit the file `/etc/nginx/mime.types`<br>
|
||||
`application/javascript js;` -> `application/javascript js mjs;`
|
||||
location ~ /(?<type>categories|products) {
|
||||
root /var/www/arming_bot/mirzaev/arming_bot/system/storage;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
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`
|
||||
location ~ \.php$ {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### SystemD (or any alternative you like)
|
||||
You can copy an example of systemd file from here: `/examples/systemd/huesos.service`<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>
|
||||
1. Execute: `sudo cp telegram-huesos.service /etc/systemd/system/telegram-huesos.service`
|
||||
|
||||
*before you execute the command think about **what it does** and whether the **paths** are specified correctly*<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>
|
||||
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`
|
||||
|
||||
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"
|
||||
```json
|
||||
{
|
||||
"status": "active", // Values: "active", "inactive" (string) Status of the settings document?
|
||||
"status": "active",
|
||||
"project": {
|
||||
"name": "PROJECT" // Name of the projext (string)
|
||||
"name": "NAME_OF_THE_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)
|
||||
},
|
||||
"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)
|
||||
}
|
||||
]
|
||||
"language": "en",
|
||||
"currency": "usd"
|
||||
}
|
||||
```
|
||||
|
||||
### Language
|
||||
Language for render of interface, if account or session language is not initialized<br/>
|
||||
<br/>
|
||||
**Value:** en<br/>
|
||||
**⚠️ The value will be converted to an instance of enumeration** `mirzaev\arming_bot\models\enumerations\language`
|
||||
|
||||
### Currency
|
||||
Currency for calculations and render of interface, if account or session currency is not initialized<br/>
|
||||
<br/>
|
||||
**Value:** usd<br/>
|
||||
**⚠️ The value will be converted to an instance of enumeration** `mirzaev\arming_bot\models\enumerations\currency`
|
||||
|
||||
## Suspensions
|
||||
*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`
|
||||
System of suspensions of chat-robot and Web App<br/>
|
||||
<br/>
|
||||
Make sure you have a **suspension** collection (can be created automatically)
|
||||
```json
|
||||
{
|
||||
"end": 1726068961, // Unixtime
|
||||
"end": 1726068961,
|
||||
"targets": {
|
||||
"chat-robot": true, // Block chat-robot
|
||||
"web app": true // Block "Web App"
|
||||
},
|
||||
"chat-robot": true,
|
||||
"web app": true
|
||||
}
|
||||
"access": {
|
||||
"tester": true, // Account with `"tester": true`
|
||||
"developer": true // Account with `"developer": true`
|
||||
"tester": true,
|
||||
"developer": true
|
||||
},
|
||||
"description": {
|
||||
"ru": "Разрабатываю каталог, поиск и корзину",
|
||||
|
@ -356,13 +119,3 @@ 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))
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,51 +1,48 @@
|
|||
{
|
||||
"name": "mirzaev/huesos",
|
||||
"description": "Chat-robot for tuning weapons",
|
||||
"homepage": "https://t.me/huesos",
|
||||
"type": "chat-robot",
|
||||
"keywords": [
|
||||
"telegram",
|
||||
"chat-robot",
|
||||
"military",
|
||||
"shop"
|
||||
],
|
||||
"readme": "README.md",
|
||||
"license": "WTFPL",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Arsen Mirzaev Tatyano-Muradovich",
|
||||
"email": "arsen@mirzaev.sexy"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.4",
|
||||
"ext-gd": "^8.4",
|
||||
"ext-intl": "^8.4",
|
||||
"triagens/arangodb": "^3.8",
|
||||
"mirzaev/minimal": "^3.4.0",
|
||||
"mirzaev/arangodb": "^2",
|
||||
"badfarm/zanzara": "^0.9.1",
|
||||
"nyholm/psr7": "^1.8",
|
||||
"react/filesystem": "^0.1.2",
|
||||
"twig/twig": "^3.10",
|
||||
"twig/extra-bundle": "^3.7",
|
||||
"twig/intl-extra": "^3.10",
|
||||
"avadim/fast-excel-reader": "^2.19",
|
||||
"ttatpuot/cdek-sdk2.0": "^1.2",
|
||||
"guzzlehttp/guzzle": "^7.9",
|
||||
"php-http/guzzle7-adapter": "^1.0",
|
||||
"react/async": "^4.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"mirzaev\\huesos\\": "mirzaev/huesos/system/"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"php-http/discovery": true,
|
||||
"wyrihaximus/composer-update-bin-autoload-path": true
|
||||
}
|
||||
}
|
||||
"name": "mirzaev/arming_bot",
|
||||
"description": "Chat-robot for tuning weapons",
|
||||
"homepage": "https://t.me/arming_bot",
|
||||
"type": "chat-robot",
|
||||
"keywords": [
|
||||
"telegram",
|
||||
"chat-robot",
|
||||
"military",
|
||||
"shop"
|
||||
],
|
||||
"readme": "README.md",
|
||||
"license": "WTFPL",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Arsen Mirzaev Tatyano-Muradovich",
|
||||
"email": "arsen@mirzaev.sexy"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"triagens/arangodb": "^3.8",
|
||||
"mirzaev/minimal": "^2.2",
|
||||
"mirzaev/arangodb": "^1.3",
|
||||
"badfarm/zanzara": "^0.9.1",
|
||||
"nyholm/psr7": "^1.8",
|
||||
"react/filesystem": "^0.1.2",
|
||||
"twig/twig": "^3.10",
|
||||
"twig/extra-bundle": "^3.7",
|
||||
"twig/intl-extra": "^3.10",
|
||||
"avadim/fast-excel-reader": "^2.19",
|
||||
"openswoole/core": "22.1.5",
|
||||
"ttatpuot/cdek-sdk2.0": "^1.2",
|
||||
"guzzlehttp/guzzle": "^7.9",
|
||||
"php-http/guzzle7-adapter": "^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"mirzaev\\arming_bot\\": "mirzaev/arming_bot/system/"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"php-http/discovery": true,
|
||||
"wyrihaximus/composer-update-bin-autoload-path": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"urn": "/account",
|
||||
"name": {
|
||||
"en": "Account",
|
||||
"ru": "Аккаунт"
|
||||
},
|
||||
"identifier": "account",
|
||||
"style": {
|
||||
"order": 1
|
||||
},
|
||||
"class": "",
|
||||
"icon": {
|
||||
"style": {},
|
||||
"class": "loading spinner animated"
|
||||
},
|
||||
"image": {
|
||||
"storage": null
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"urn": "/cart",
|
||||
"name": {
|
||||
"en": "Cart",
|
||||
"ru": "Корзина"
|
||||
},
|
||||
"style": {
|
||||
"order": 999
|
||||
},
|
||||
"class": "cart",
|
||||
"icon": {
|
||||
"style": {},
|
||||
"class": "shopping cart"
|
||||
},
|
||||
"image": {
|
||||
"storage": null
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"urn": "/",
|
||||
"name": {
|
||||
"en": "Main page",
|
||||
"ru": "Главная страница"
|
||||
},
|
||||
"style": {
|
||||
"order": 0
|
||||
},
|
||||
"class": "",
|
||||
"icon": {
|
||||
"style": {},
|
||||
"class": "house"
|
||||
},
|
||||
"image": {
|
||||
"storage": null
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"end": 1726068961,
|
||||
"targets": {
|
||||
"chat-robot": true,
|
||||
"web app": true
|
||||
},
|
||||
"access": {
|
||||
"tester": true,
|
||||
"developer": true
|
||||
},
|
||||
"description": {
|
||||
"ru": "Разрабатываю каталог, поиск и корзину",
|
||||
"en": "I am developing a catalog, search and cart"
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"type": "search-alias",
|
||||
"name": "products_search",
|
||||
"indexes": [
|
||||
{
|
||||
"collection": "product",
|
||||
"index": "name_ru"
|
||||
},
|
||||
{
|
||||
"collection": "product",
|
||||
"index": "description_ru"
|
||||
},
|
||||
{
|
||||
"collection": "product",
|
||||
"index": "compatibility_ru"
|
||||
}
|
||||
],
|
||||
"id": "1368785",
|
||||
"globallyUniqueId": "hB561949FBEF8/1368785"
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
#
|
||||
# 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;
|
||||
|
||||
# root /var/www/huesos/mirzaev/huesos/system/public;
|
||||
|
||||
# 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;
|
||||
|
||||
# location / {
|
||||
# try_files $uri $uri/ /index.php?$query_string;
|
||||
# }
|
||||
|
||||
# location /api/cdek {
|
||||
# rewrite ^/api/cdek(.*)$ /$1 break;
|
||||
# index cdek.php;
|
||||
# }
|
||||
|
||||
# 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;
|
||||
# }
|
||||
# }
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
|
||||
server_name domain.zone;
|
||||
|
||||
if ($host = domain.zone) {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
return 404;
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\arming_bot\controllers;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\arming_bot\controllers\core,
|
||||
mirzaev\arming_bot\models\session,
|
||||
mirzaev\arming_bot\models\account as model;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\document;
|
||||
|
||||
/**
|
||||
* Controller of account
|
||||
*
|
||||
* @package mirzaev\arming_bot\controllers
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class account extends core
|
||||
{
|
||||
/**
|
||||
* Registry of errors
|
||||
*/
|
||||
protected array $errors = [
|
||||
'session' => [],
|
||||
'account' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* Write to the buffer
|
||||
*
|
||||
* @param array $parameters Parameters of the request (POST + GET)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function write(array $parameters = []): void
|
||||
{
|
||||
if (!empty($parameters) && $this->account instanceof model) {
|
||||
// Received parameters and initialized account
|
||||
|
||||
// Declaring the buffer of deserialized parameters
|
||||
$deserialized = [];
|
||||
|
||||
foreach ($parameters as $name => $value) {
|
||||
// Iterate over parameters
|
||||
|
||||
// Validation of the parameter value
|
||||
if (mb_strlen($value) > 4096) continue;
|
||||
|
||||
// Declaring the buffer of deserialized parameter
|
||||
$parameter = null;
|
||||
|
||||
// Deserializing name to multidimensional array
|
||||
foreach (array_reverse(explode('_', $name)) as $key)
|
||||
$parameter = [$key => $parameter ?? (json_validate($value) ? json_decode($value, true, 10) : $value)];
|
||||
|
||||
// Writing into the buffer of deserialized parameters
|
||||
$deserialized = array_merge_recursive($parameter, $deserialized);
|
||||
}
|
||||
|
||||
|
||||
// Write to the account document from ArangoDB
|
||||
if (!empty($deserialized)) $this->account->write($deserialized, $this->errors['account']);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,344 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\arming_bot\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;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\document;
|
||||
|
||||
/**
|
||||
* Controller of cart
|
||||
*
|
||||
* @package mirzaev\arming_bot\controllers
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class cart extends core
|
||||
{
|
||||
/**
|
||||
* Instance of the cart
|
||||
*/
|
||||
protected readonly ?model $cart;
|
||||
|
||||
/**
|
||||
* Registry of errors
|
||||
*/
|
||||
protected array $errors = [
|
||||
'session' => [],
|
||||
'account' => [],
|
||||
'cart' => [],
|
||||
'menu' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* Cart
|
||||
*
|
||||
* @param array $parameters Parameters of the request (POST + GET)
|
||||
*/
|
||||
public function index(array $parameters = []): ?string
|
||||
{
|
||||
if (isset($menu)) {
|
||||
//
|
||||
|
||||
} else {
|
||||
// Not received ... menu
|
||||
|
||||
// Search for filters and write to the buffer of global variables of view templater
|
||||
$this->view->menu = menu::_read(
|
||||
return: 'MERGE(d, { name: d.name.@language })',
|
||||
sort: 'd.style.order ASC, d.created DESC, d._key DESC',
|
||||
amount: 4,
|
||||
parameters: ['language' => $this->language->name],
|
||||
errors: $this->errors['menu']
|
||||
);
|
||||
}
|
||||
|
||||
// Initializing the cart
|
||||
$this->cart = $this->account?->cart() ?? $this->session?->cart();
|
||||
|
||||
// Initializing the cart data
|
||||
$this->view->cart = [
|
||||
'summary' => $this->cart?->summary(currency: $this->currency),
|
||||
'products' => $this->cart?->products(language: $this->language, currency: $this->currency)
|
||||
];
|
||||
|
||||
// Initializing types of avaiabld deliveries
|
||||
$this->view->deliveries = [
|
||||
'cdek' => [
|
||||
'label' => 'CDEK'
|
||||
]
|
||||
];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
// GET request
|
||||
|
||||
// Exit (success)
|
||||
return $this->view->render('cart/page.html', [
|
||||
'h2' => $this->language === language::ru ? 'Корзина' : 'Cart' // @see https://git.mirzaev.sexy/mirzaev/huesos/issues/1
|
||||
]);
|
||||
} else if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// POST request
|
||||
|
||||
// Initializing a response headers
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Initializing of the output buffer
|
||||
ob_start();
|
||||
|
||||
// Generating the reponse
|
||||
echo json_encode(
|
||||
[
|
||||
'main' => '',
|
||||
'errors' => $this->errors
|
||||
]
|
||||
);
|
||||
|
||||
// Initializing a response headers
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Sending and deinitializing of the output buffer
|
||||
ob_end_flush();
|
||||
flush();
|
||||
|
||||
// Exit (success)
|
||||
return null;
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Product
|
||||
*
|
||||
* Write or delete the product in the cart
|
||||
*
|
||||
* @param array $parameters Parameters of the request (POST + GET)
|
||||
*
|
||||
* @todo
|
||||
* 1. Add a limit on adding products to the cart based on the number of products in stock
|
||||
*/
|
||||
public function product(array $parameters = []): ?string
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// POST request
|
||||
|
||||
// Declaring of the buffer with amount of the product in the cart
|
||||
$amount = 0;
|
||||
|
||||
// Validating @todo add throwing errors
|
||||
$identifier = null;
|
||||
if (isset($parameters['identifier']) && preg_match('/[\d]+/', urldecode($parameters['identifier']), $matches)) $identifier = (int) $matches[0];
|
||||
|
||||
if (isset($identifier)) {
|
||||
// Received and validated identfier of the product
|
||||
|
||||
// Search for the product
|
||||
$product = product::read(
|
||||
filter: "d.identifier == @identifier && d.deleted != true && d.hidden != true",
|
||||
sort: 'd.created DESC',
|
||||
amount: 1,
|
||||
parameters: ['identifier' => $identifier],
|
||||
errors: $this->errors['cart']
|
||||
);
|
||||
|
||||
if ($product instanceof product) {
|
||||
// Initialized the product
|
||||
|
||||
// Initializing the cart
|
||||
$this->cart = $this->account?->cart() ?? $this->session?->cart();
|
||||
|
||||
if ($this->cart instanceof model) {
|
||||
// Initialized the cart
|
||||
|
||||
// Initializing the buffer with amount of the product in the cart
|
||||
$amount = $this->cart->count(product: $product, limit: 100, errors: $this->errors['cart']) ?? 0;
|
||||
|
||||
if ($this->cart instanceof model) {
|
||||
// Initialized the cart
|
||||
|
||||
// Validating @todo add throwing errors
|
||||
$type = null;
|
||||
if (isset($parameters['type']) && preg_match('/[\w]+/', urldecode($parameters['type']), $matches)) $type = $matches[0];
|
||||
|
||||
if (isset($type)) {
|
||||
// Received and validated type of action with the product
|
||||
|
||||
if ($type === 'toggle') {
|
||||
// Write the product to the cart if is not in the cart and vice versa
|
||||
|
||||
if ($amount > 0) {
|
||||
// The cart has the product
|
||||
|
||||
// Deleting the product from the cart
|
||||
$this->cart->delete(product: $product, amount: $amount, errors: $this->errors['cart']);
|
||||
|
||||
// Reinitializing the buffer with amount of the product in the cart
|
||||
$amount = 0;
|
||||
} else {
|
||||
// The cart has no the product
|
||||
|
||||
// Writing the product to the cart
|
||||
$this->cart->write(product: $product, amount: 1, errors: $this->errors['cart']);
|
||||
|
||||
// Reinitializing the buffer with amount of the product in the cart
|
||||
$amount = 1;
|
||||
}
|
||||
} else {
|
||||
// Received not the "toggle" command
|
||||
|
||||
// Validating @todo add throwing errors
|
||||
$_amount = null;
|
||||
if (isset($parameters['amount']) && preg_match('/[\d]+/', urldecode($parameters['amount']), $matches)) $_amount = (int) $matches[0];
|
||||
|
||||
if (isset($_amount)) {
|
||||
// Received and validated amount parameter for action with the product
|
||||
|
||||
if ($type === 'write') {
|
||||
// Increase amount of the product in the cart
|
||||
|
||||
if ($amount + $_amount < 101) {
|
||||
// Validated amount to wrting
|
||||
|
||||
// Writing the product to the cart
|
||||
$this->cart->write(product: $product, amount: $_amount, errors: $this->errors['cart']);
|
||||
|
||||
// Reinitialize the buffer with amount of the product in the cart
|
||||
$amount += $_amount;
|
||||
}
|
||||
} else if ($type === 'delete') {
|
||||
// Decrease amount of the product in the cart
|
||||
|
||||
if ($amount - $_amount > -1) {
|
||||
// Validated amount to deleting
|
||||
|
||||
// Deleting the product from the cart
|
||||
$this->cart->delete(product: $product, amount: $_amount, errors: $this->errors['cart']);
|
||||
|
||||
// Reinitialize the buffer with amount of the product in the cart
|
||||
$amount -= $_amount;
|
||||
}
|
||||
} else if ($type === 'set') {
|
||||
// Set amount of the product in the cart
|
||||
|
||||
if ($_amount > -1 && $_amount < 101) {
|
||||
// Validated amount to setting
|
||||
|
||||
if ($_amount > $amount) {
|
||||
// Requested amount more than actual amount of the product in the cart
|
||||
|
||||
// Writing the product from the cart
|
||||
$this->cart->write(product: $product, amount: $_amount - $amount, errors: $this->errors['cart']);
|
||||
} else {
|
||||
// Requested amount less than actual amount of the product in the cart
|
||||
|
||||
// Deleting the product from the cart
|
||||
$this->cart->delete(product: $product, amount: $amount - $_amount, errors: $this->errors['cart']);
|
||||
}
|
||||
|
||||
// Reinitializing the buffer with amount of the product in the cart
|
||||
$amount = $_amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initializing a response headers
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Initializing of the output buffer
|
||||
ob_start();
|
||||
|
||||
// Generating the reponse
|
||||
echo json_encode(
|
||||
[
|
||||
'amount' => $amount, // $amount does not store a real value, but is calculated without a repeated request to ArangoDB
|
||||
'errors' => $this->errors
|
||||
]
|
||||
);
|
||||
|
||||
// Initializing a response headers
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Sending and deinitializing of the output buffer
|
||||
ob_end_flush();
|
||||
flush();
|
||||
|
||||
// Exit (success)
|
||||
return null;
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary
|
||||
*
|
||||
* @param array $parameters Parameters of the request (POST + GET)
|
||||
*/
|
||||
public function summary(array $parameters = []): ?string
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// POST request
|
||||
|
||||
// Initializing the cart
|
||||
$this->cart = $this->account?->cart() ?? $this->session?->cart();
|
||||
|
||||
if ($this->cart instanceof model) {
|
||||
// Initialized the cart
|
||||
|
||||
// Initializing summary data of the cart
|
||||
$summary = $this->cart?->summary(currency: $this->currency, errors: $this->errors['cart']);
|
||||
|
||||
// Initializing response headers
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Initializing of the output buffer
|
||||
ob_start();
|
||||
|
||||
// Generating the reponse
|
||||
echo json_encode(
|
||||
[
|
||||
'cost' => $summary['cost'] ?? 0,
|
||||
'amount' => $summary['amount'] ?? 0,
|
||||
'errors' => $this->errors
|
||||
]
|
||||
);
|
||||
|
||||
// Initializing a response headers
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Sending and deinitializing of the output buffer
|
||||
ob_end_flush();
|
||||
flush();
|
||||
|
||||
// Exit (success)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,435 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\arming_bot\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\menu;
|
||||
|
||||
// Library for ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
/**
|
||||
* Controller of catalog
|
||||
*
|
||||
* @package mirzaev\arming_bot\controllers
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class catalog extends core
|
||||
{
|
||||
/**
|
||||
* Registry of errors
|
||||
*/
|
||||
protected array $errors = [
|
||||
'session' => [],
|
||||
'account' => [],
|
||||
'menu' => [],
|
||||
'catalog' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* Catalog
|
||||
*
|
||||
* @param array $parameters Parameters of the request (POST + GET)
|
||||
*/
|
||||
public function index(array $parameters = []): ?string
|
||||
{
|
||||
// validating
|
||||
if (isset($parameters['product']) && preg_match('/[\d]+/', $parameters['product'], $matches)) $product = (int) $matches[0];
|
||||
|
||||
if (isset($product)) {
|
||||
// Received and validated identifier of the product
|
||||
|
||||
// Search for the product data and write to the buffer of global variables of view templater
|
||||
$this->view->product = product::read(
|
||||
filter: "d.identifier == @identifier && d.deleted != true && d.hidden != true",
|
||||
sort: 'd.created DESC',
|
||||
amount: 1,
|
||||
return: '{identifier: d.identifier, name: d.name.@language, description: d.description.@language, cost: d.cost.@currency, weight: d.weight, dimensions: d.dimensions, brand: d.brand.@language, compatibility: d.compatibility.@language, cost: d.cost.@currency, images: d.images[*].storage}',
|
||||
language: $this->language,
|
||||
currency: $this->currency,
|
||||
parameters: ['identifier' => $product],
|
||||
errors: $this->errors['catalog']
|
||||
)?->getAll();
|
||||
}
|
||||
|
||||
// Intializing buffer of query parameters
|
||||
$_parameters = [];
|
||||
|
||||
// Initializing buffer of filters query (AQL)
|
||||
$_filters = 'd.deleted != true && d.hidden != true';
|
||||
|
||||
// Validating
|
||||
if (isset($parameters['brand']) && preg_match('/[\w\s]+/u', urldecode($parameters['brand']), $matches)) $brand = $matches[0];
|
||||
|
||||
if (isset($brand)) {
|
||||
// Received and validated filter by brand
|
||||
|
||||
// Writing to the account buffer
|
||||
$this->account?->write(
|
||||
[
|
||||
'catalog' => [
|
||||
'filters' => [
|
||||
'brand' => $brand
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
// Writing to the session buffer
|
||||
$this->session?->write(
|
||||
[
|
||||
'catalog' => [
|
||||
'filters' => [
|
||||
'brand' => $brand
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
// Writing to the buffer of filters query (AQL)
|
||||
$_filters .= ' && d.brand.@language == @brand';
|
||||
|
||||
// Writing to the buffer of query parameters
|
||||
$_parameters['brand'] = $brand;
|
||||
} else {
|
||||
// Not received or not validated filter by brand
|
||||
|
||||
// Writing to the account buffer
|
||||
$this->account?->write(
|
||||
[
|
||||
'catalog' => [
|
||||
'filters' => [
|
||||
'brand' => null
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
// Writing to the session buffer
|
||||
$this->session?->write(
|
||||
[
|
||||
'catalog' => [
|
||||
'filters' => [
|
||||
'brand' => null
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Initialize buffer of filters query (AQL)
|
||||
$_sort = 'd.position ASC, d.name ASC, d.created DESC';
|
||||
|
||||
// Validating
|
||||
if (isset($parameters['sort']) && preg_match('/[\w\s]+/u', $parameters['sort'], $matches)) $sort = $matches[0];
|
||||
|
||||
if (isset($sort)) {
|
||||
// Received and validated sort
|
||||
|
||||
// Writing to the account buffer
|
||||
$this->account?->write(
|
||||
[
|
||||
'catalog' => [
|
||||
'sort' => $sort
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
// Writing to the session buffer
|
||||
$this->session?->write(
|
||||
[
|
||||
'catalog' => [
|
||||
'sort' => $sort
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
// Write to the buffer of sort query (AQL)
|
||||
$_sort = "d.@sort DESC, $_sort";
|
||||
|
||||
// Write to the buffer of query parameters
|
||||
$_parameters['sort'] = $sort;
|
||||
} else {
|
||||
// Not received or not validated filter by brand
|
||||
|
||||
// Writing to the account buffer
|
||||
$this->account?->write(
|
||||
[
|
||||
'catalog' => [
|
||||
'sort' => null
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
// Writing to the session buffer
|
||||
$this->session?->write(
|
||||
[
|
||||
'catalog' => [
|
||||
'sort' => null
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Validating @todo add throwing errors
|
||||
if (isset($parameters['text']) && preg_match('/[\w\s]+/u', urldecode($parameters['text']), $matches) && mb_strlen($matches[0]) > 2) $text = $matches[0];
|
||||
|
||||
if (isset($text)) {
|
||||
// Received and validated text
|
||||
|
||||
// Writing to the account buffer (useless becouse rewrite itself to null with every request)
|
||||
$this->account?->write(
|
||||
[
|
||||
'catalog' => [
|
||||
'search' => [
|
||||
'text' => $text
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
// Writing to the session buffer (useless becouse rewrite itself to null with every request)
|
||||
$this->session?->write(
|
||||
[
|
||||
'catalog' => [
|
||||
'search' => [
|
||||
'text' => $text
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
} else {
|
||||
// Not received or not validated filter by brand
|
||||
|
||||
// Writing to the account buffer
|
||||
$this->account?->write(
|
||||
[
|
||||
'catalog' => [
|
||||
'search' => [
|
||||
'text' => null
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
// Writing to the session buffer
|
||||
$this->session?->write(
|
||||
[
|
||||
'catalog' => [
|
||||
'search' => [
|
||||
'text' => null
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Validating
|
||||
if (isset($parameters['category']) && preg_match('/[\d]+/', $parameters['category'], $matches)) $category = (int) $matches[0];
|
||||
|
||||
if (isset($category)) {
|
||||
// Received and validated identifier of the category
|
||||
|
||||
// Initialize of category
|
||||
$category = category::_read('d.identifier == @identifier', parameters: ['identifier' => $category], errors: $this->errors['catalog']);
|
||||
|
||||
if ($category instanceof category) {
|
||||
// Found the category
|
||||
|
||||
// Write to the response buffer
|
||||
$response['category'] = ['name' => $category->name ?? null];
|
||||
|
||||
// Search for categories that are descendants of $category
|
||||
$entries = entry::search(
|
||||
document: $category,
|
||||
amount: 50,
|
||||
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 = [];
|
||||
|
||||
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 = $category;
|
||||
|
||||
// Write to the buffer of global variables of view templater
|
||||
$this->view->products = $product;
|
||||
|
||||
if (isset($this->view->products) && count($this->view->products) > 0) {
|
||||
// Amount of rendered products is more than 0
|
||||
|
||||
// Write to the buffer of filters query (AQL)
|
||||
$_filters .= ' && POSITION(["' . implode('", "', array_map(fn(_document $document): string => $document->getId(), $this->view->products)) . '"], d._id)';
|
||||
}
|
||||
|
||||
// Deleting buffers
|
||||
unset($category, $product);
|
||||
}
|
||||
} else if (!isset($parameters['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($brand) || isset($text) || (isset($this->view->products) && count($this->view->products) > 0)) {
|
||||
// Received and validated at least one of filters or amount of rendered products is more than 0
|
||||
|
||||
// Search for filters and write to the buffer of global variables of view templater
|
||||
$this->view->filters = [
|
||||
'brands' => product::collect(
|
||||
return: 'd.brand.@language',
|
||||
products: array_map(fn(_document $document): string => $document->getId(), $this->view->products ?? []),
|
||||
language: $this->language,
|
||||
errors: $this->errors['catalog']
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
// Search among products in the $category
|
||||
if (isset($text) || isset($this->view->products) && count($this->view->products) > 0) {
|
||||
// Amount of rendered products is more than 0
|
||||
|
||||
// Search for products and write to the buffer of global variables of view templater
|
||||
$this->view->products = product::read(
|
||||
search: $text ?? null,
|
||||
filter: $_filters,
|
||||
sort: $_sort,
|
||||
amount: 50, // @todo pagination
|
||||
return: 'DISTINCT MERGE(d, {name: d.name.@language, description: d.description.@language, compatibility: d.compatibility.@language, brand: d.brand.@language, cost: d.cost.@currency})',
|
||||
language: $this->language,
|
||||
currency: $this->currency,
|
||||
parameters: $_parameters,
|
||||
errors: $this->errors['catalog']
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($menu)) {
|
||||
//
|
||||
|
||||
} else {
|
||||
// Not received ... menu
|
||||
|
||||
// Search for filters and write to the buffer of global variables of view templater
|
||||
$this->view->menu = menu::_read(
|
||||
return: 'MERGE(d, { name: d.name.@language })',
|
||||
sort: 'd.style.order ASC, d.created DESC, d._key DESC',
|
||||
amount: 4,
|
||||
parameters: ['language' => $this->language->name],
|
||||
errors: $this->errors['menu']
|
||||
);
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
// GET request
|
||||
|
||||
if (!empty($this->view->product)) {
|
||||
// Initialized the product data
|
||||
|
||||
// Writing javascript code that has been executed after core and damper has been loaded
|
||||
$this->view->javascript = [
|
||||
sprintf(
|
||||
<<<javascript
|
||||
if (typeof _window === 'undefined') {
|
||||
_window = setTimeout(() => core.catalog.product.system('%s'), 500);
|
||||
}
|
||||
javascript,
|
||||
$this->view->product['identifier']
|
||||
)
|
||||
] + ($this->view->javascript ?? []);
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
return $this->view->render('catalog/page.html', [
|
||||
'h2' => $this->language === language::ru ? 'Каталог' : 'Catalog' // @see https://git.mirzaev.sexy/mirzaev/huesos/issues/1
|
||||
]);
|
||||
} else if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// POST request
|
||||
|
||||
// Initializing the buffer of response
|
||||
$response = [
|
||||
'title' => $this->language === language::ru ? 'Каталог' : 'Catalog' // @see https://git.mirzaev.sexy/mirzaev/huesos/issues/1
|
||||
];
|
||||
|
||||
if (isset($this->view->categories)) {
|
||||
// Initialized categories
|
||||
|
||||
// Render HTML-code of categories and write to the response buffer
|
||||
$response['categories'] = $this->view->render('catalog/elements/categories.html');
|
||||
}
|
||||
|
||||
if (isset($this->view->product)) {
|
||||
// Initialized product
|
||||
|
||||
// Writing data of the product to the response buffer @todo GENERATE THIS ON THE SERVER
|
||||
$response['product'] = $this->view->product;
|
||||
}
|
||||
|
||||
if (isset($this->view->products)) {
|
||||
// Initialized products
|
||||
|
||||
// Render HTML-code of products and write to the response buffer
|
||||
$response['products'] = $this->view->render('catalog/elements/products.html');
|
||||
}
|
||||
|
||||
if (isset($this->view->filters)) {
|
||||
// Initialized filters
|
||||
|
||||
// Render HTML-code of filters and write to the response buffer
|
||||
$response['filters'] = $this->view->render('catalog/elements/filters.html');
|
||||
}
|
||||
|
||||
// Initializing a response headers
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Initializing of the output buffer
|
||||
ob_start();
|
||||
|
||||
// Generating the reponse
|
||||
echo json_encode(
|
||||
$response + [
|
||||
'errors' => $this->errors
|
||||
]
|
||||
);
|
||||
|
||||
// Initializing a response headers
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Sending and deinitializing of the output buffer
|
||||
ob_end_flush();
|
||||
flush();
|
||||
|
||||
// Exit (success)
|
||||
return null;
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -2,40 +2,26 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\controllers;
|
||||
namespace mirzaev\arming_bot\controllers;
|
||||
|
||||
// Files of the project
|
||||
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;
|
||||
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;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\core as minimal,
|
||||
mirzaev\minimal\controller,
|
||||
mirzaev\minimal\http\response,
|
||||
mirzaev\minimal\http\enumerations\status;
|
||||
use mirzaev\minimal\controller;
|
||||
|
||||
/**
|
||||
* Core of controllers
|
||||
*
|
||||
* @package mirzaev\huesos\controllers
|
||||
*
|
||||
* @param settings $settings Instance of the settings
|
||||
* @param session $session Instance of the session
|
||||
* @param account|null $account Instance of the account
|
||||
* @param cart|null $cart Instance of the cart
|
||||
* @param language $language Language
|
||||
* @param currency $currency Currency
|
||||
* @param response $response Response
|
||||
* @param array $errors Registry of errors
|
||||
*
|
||||
* @method void __construct(minimal $core, bool $initialize) Constructor
|
||||
* @package mirzaev\arming_bot\controllers
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
|
@ -43,63 +29,42 @@ use mirzaev\minimal\core as minimal,
|
|||
class core extends controller
|
||||
{
|
||||
/**
|
||||
* Settings
|
||||
*
|
||||
* @var settings $settings Instance of the settings
|
||||
* Postfix for name of controllers files
|
||||
*/
|
||||
final public const string POSTFIX = '';
|
||||
|
||||
/**
|
||||
* Instance of the settings
|
||||
*/
|
||||
protected readonly settings $settings;
|
||||
|
||||
/**
|
||||
* Session
|
||||
*
|
||||
* @var session|null $session Instance of the session
|
||||
* Instance of the session
|
||||
*/
|
||||
protected readonly session $session;
|
||||
|
||||
/**
|
||||
* Account
|
||||
*
|
||||
* @var account|null $account Instance of the account
|
||||
* Instance of the account
|
||||
*/
|
||||
protected readonly ?account $account;
|
||||
|
||||
/**
|
||||
* Cart
|
||||
*
|
||||
* @var cart|null $cart Instance of the cart
|
||||
* Instance of the cart
|
||||
*/
|
||||
protected readonly ?cart $cart;
|
||||
|
||||
/**
|
||||
* Language
|
||||
*
|
||||
* @var language $language Language
|
||||
*/
|
||||
protected language $language = language::en;
|
||||
|
||||
/**
|
||||
* Currency
|
||||
*
|
||||
* @var currency $currency Currency
|
||||
*/
|
||||
protected currency $currency = currency::usd;
|
||||
|
||||
/**
|
||||
* Response
|
||||
*
|
||||
* @see https://wiki.php.net/rfc/property-hooks (find a table about backed and virtual hooks)
|
||||
*
|
||||
* @var response $response Response
|
||||
*/
|
||||
protected response $response {
|
||||
// Read
|
||||
get => $this->response ??= $this->request->response();
|
||||
}
|
||||
|
||||
/**
|
||||
* Errors
|
||||
*
|
||||
* @var array $errors Registry of errors
|
||||
* Registry of errors
|
||||
*/
|
||||
protected array $errors = [
|
||||
'session' => [],
|
||||
|
@ -107,9 +72,8 @@ class core extends controller
|
|||
];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* Constructor of an instance
|
||||
*
|
||||
* @param minimal $core Initialize a controller?
|
||||
* @param bool $initialize Initialize a controller?
|
||||
*
|
||||
* @return void
|
||||
|
@ -118,13 +82,13 @@ class core extends controller
|
|||
* 1. settings account и session не имеют проверок на возврат null
|
||||
* 2. TRANSIT EVERYTHING TO MIDDLEWARES
|
||||
*/
|
||||
public function __construct(minimal $core, bool $initialize = true)
|
||||
public function __construct(bool $initialize = true)
|
||||
{
|
||||
// Blocking requests from CloudFlare (better to write this blocking into nginx config file)
|
||||
if (isset($_SERVER['HTTP_USER_AGENT']) && $_SERVER['HTTP_USER_AGENT'] === 'nginx-ssl early hints') return status::bruh->label;
|
||||
if (isset($_SERVER['HTTP_USER_AGENT']) && $_SERVER['HTTP_USER_AGENT'] === 'nginx-ssl early hints') return;
|
||||
|
||||
// For the extends system
|
||||
parent::__construct(core: $core);
|
||||
parent::__construct($initialize);
|
||||
|
||||
if ($initialize) {
|
||||
// Initializing is requested
|
||||
|
@ -143,7 +107,9 @@ class core extends controller
|
|||
|
||||
// Handle a problems with initializing a session
|
||||
if (!empty($this->errors['session'])) exit(1);
|
||||
else if ($_COOKIE["session"] !== $this->session->hash) {
|
||||
|
||||
// телеграм не сохраняет куки
|
||||
/* else if ($_COOKIE["session"] !== $this->session->hash) {
|
||||
// Hash of the session is changed (implies that the session has expired and recreated)
|
||||
|
||||
// Write a new hash of the session to cookies
|
||||
|
@ -158,16 +124,13 @@ class core extends controller
|
|||
'samesite' => 'strict'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Initializing registry of account errors
|
||||
$this->errors['account'];
|
||||
} */
|
||||
|
||||
// Initializing of the account
|
||||
$this->account = $this->session->account($this->errors['account']);
|
||||
|
||||
// Initializing of the settings
|
||||
$this->settings = settings::active(create: SETTINGS_PROJECT);
|
||||
$this->settings = settings::active();
|
||||
|
||||
// Initializing of the language
|
||||
$this->language = $this->account?->language ?? $this->session?->buffer['language'] ?? $this->settings?->language ?? language::en;
|
||||
|
@ -241,4 +204,21 @@ class core extends controller
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check of initialization
|
||||
*
|
||||
* Checks whether a property is initialized in a document instance from ArangoDB
|
||||
*
|
||||
* @param string $name Name of the property from ArangoDB
|
||||
*
|
||||
* @return bool The property is initialized?
|
||||
*/
|
||||
public function __isset(string $name): bool
|
||||
{
|
||||
// Check of initialization of the property and exit (success)
|
||||
return match ($name) {
|
||||
default => isset($this->{$name})
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\arming_bot\controllers;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\arming_bot\controllers\core,
|
||||
mirzaev\arming_bot\models\enumerations\language,
|
||||
mirzaev\arming_bot\models\cart,
|
||||
mirzaev\arming_bot\models\product,
|
||||
mirzaev\arming_bot\models\menu;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\document;
|
||||
|
||||
/**
|
||||
* Controller of cdek
|
||||
*
|
||||
* @package mirzaev\arming_bot\controllers
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class cdek extends core
|
||||
{
|
||||
/**
|
||||
* Registry of errors
|
||||
*/
|
||||
protected array $errors = [
|
||||
'delivery' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* Calculate
|
||||
*
|
||||
* @param array $parameters Parameters of the request (POST + GET)
|
||||
*/
|
||||
public function calculate(array $parameters = []): ?string
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// POST request
|
||||
|
||||
$this->model::calculate();
|
||||
|
||||
die;
|
||||
|
||||
// Initializing a response headers
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Initializing of the output buffer
|
||||
ob_start();
|
||||
|
||||
// Generating the reponse
|
||||
echo json_encode(
|
||||
[
|
||||
'main' => '',
|
||||
'errors' => $this->errors
|
||||
]
|
||||
);
|
||||
|
||||
// Initializing a response headers
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Sending and deinitializing of the output buffer
|
||||
ob_end_flush();
|
||||
flush();
|
||||
|
||||
// Exit (success)
|
||||
return null;
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\arming_bot\controllers;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\arming_bot\controllers\core,
|
||||
mirzaev\arming_bot\models\product;
|
||||
|
||||
/**
|
||||
* Index controller
|
||||
*
|
||||
* @package mirzaev\arming_bot\controllers
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class index extends core
|
||||
{
|
||||
|
||||
/**
|
||||
* Render the main page
|
||||
*
|
||||
* @param array $parameters Parameters of the request (POST + GET)
|
||||
*/
|
||||
public function index(array $parameters = []): ?string
|
||||
{
|
||||
// Exit (success)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') return $this->view->render('index.html');
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\arming_bot\controllers;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\arming_bot\controllers\core,
|
||||
mirzaev\arming_bot\models\session as model,
|
||||
mirzaev\arming_bot\models\account;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\document;
|
||||
|
||||
/**
|
||||
* Controller of session
|
||||
*
|
||||
* @package mirzaev\arming_bot\controllers
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class session extends core
|
||||
{
|
||||
/**
|
||||
* Registry of errors
|
||||
*/
|
||||
protected array $errors = [
|
||||
'session' => [],
|
||||
'account' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* Connect session to the telegram account
|
||||
*
|
||||
* @param array $parameters Parameters of the request (POST + GET)
|
||||
*/
|
||||
public function telegram(array $parameters = []): ?string
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// POST request
|
||||
|
||||
// Declaring variables in the correct scope
|
||||
$identifier = $domain = $language = null;
|
||||
|
||||
if ($connected = isset($this->account)) {
|
||||
// Found the account
|
||||
|
||||
// Initializing identifier of the account
|
||||
$identifier = $this->account->identifier;
|
||||
|
||||
// Initializing language of the account
|
||||
$language = $this->account->language;
|
||||
|
||||
// Initializing domain of the account
|
||||
$domain = $this->account->domain;
|
||||
} else {
|
||||
// Not found the account
|
||||
|
||||
if (count($parameters) > 1 && isset($parameters['hash'])) {
|
||||
|
||||
$buffer = $parameters;
|
||||
|
||||
unset($buffer['hash']);
|
||||
ksort($buffer);
|
||||
|
||||
$prepared = [];
|
||||
foreach ($buffer as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$prepared[] = $key . '=' . json_encode($value, JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
$prepared[] = $key . '=' . $value;
|
||||
}
|
||||
}
|
||||
|
||||
$key = hash_hmac('sha256', require(SETTINGS . DIRECTORY_SEPARATOR . 'key.php'), 'WebAppData', true);
|
||||
$hash = bin2hex(hash_hmac('sha256', implode(PHP_EOL, $prepared), $key, true));
|
||||
|
||||
if (hash_equals($hash, $parameters['hash'])) {
|
||||
// Data confirmed (according to telegram documentation)
|
||||
|
||||
if (time() - $parameters['auth_date'] < 86400) {
|
||||
// Authorization date less than 1 day ago
|
||||
|
||||
// Initializing data of the account
|
||||
$data = json_decode($parameters['user']);
|
||||
|
||||
// Initializing of the account
|
||||
$account = account::initialize(
|
||||
$data->id,
|
||||
[
|
||||
'identifier' => $data->id,
|
||||
'name' => [
|
||||
'first' => $data->first_name,
|
||||
'last' => $data->last_name
|
||||
],
|
||||
'domain' => $data->username,
|
||||
'language' => $data->language_code,
|
||||
'messages' => $data->allows_write_to_pm
|
||||
],
|
||||
$this->errors['account']
|
||||
);
|
||||
|
||||
if ($account instanceof account) {
|
||||
// Initialized the account
|
||||
|
||||
// Connecting the account to the session
|
||||
$connected = $this->session->connect($account, $this->errors['session']);
|
||||
|
||||
// Initializing identifier of the account
|
||||
$identifier = $account->identifier;
|
||||
|
||||
// Initializing language of the account
|
||||
$language = $account->language;
|
||||
|
||||
// Initializing domain of the account
|
||||
$domain = $account->domain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initializing a response headers
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Initializing of the output buffer
|
||||
ob_start();
|
||||
|
||||
// Generating the reponse
|
||||
echo json_encode(
|
||||
[
|
||||
'connected' => (bool) $connected,
|
||||
'identifier' => $identifier ?? null,
|
||||
'domain' => $domain ?? null,
|
||||
'language' => $language?->name ?? null,
|
||||
'errors' => $this->errors
|
||||
]
|
||||
);
|
||||
|
||||
// Initializing a response headers
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Sending and deinitializing of the output buffer
|
||||
ob_end_flush();
|
||||
flush();
|
||||
|
||||
// Exit (success)
|
||||
return null;
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to the buffer
|
||||
*
|
||||
* @param array $parameters Parameters of the request (POST + GET)
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @todo переделать под trait buffer
|
||||
*/
|
||||
public function write(array $parameters = []): void
|
||||
{
|
||||
if (!empty($parameters) && $this->session instanceof model) {
|
||||
// Received parameters and initialized session
|
||||
|
||||
// Declaring the buffer of deserialized parameters
|
||||
$deserialized = [];
|
||||
|
||||
foreach ($parameters as $name => $value) {
|
||||
// Iterate over parameters
|
||||
|
||||
// Validation of the parameter value
|
||||
if (mb_strlen($value) > 4096) continue;
|
||||
|
||||
// Declaring the buffer of deserialized parameter
|
||||
$parameter = null;
|
||||
|
||||
// Deserializing name to multidimensional array
|
||||
foreach (array_reverse(explode('_', $name)) as $key)
|
||||
$parameter = [$key => $parameter ?? (json_validate($value) ? json_decode($value, true, 10) : $value)];
|
||||
|
||||
// Writing into the buffer of deserialized parameters
|
||||
$deserialized = array_merge_recursive($parameter, $deserialized);
|
||||
}
|
||||
|
||||
// Write to the session document from ArangoDB
|
||||
if (!empty($deserialized)) $this->session->write($deserialized, $this->errors['session']);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,18 +2,18 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models;
|
||||
namespace mirzaev\arming_bot\models;
|
||||
|
||||
// Files of the project
|
||||
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;
|
||||
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;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
|
@ -31,7 +31,7 @@ use exception;
|
|||
/**
|
||||
* Model of account
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
* @package mirzaev\arming_bot\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
|
@ -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
|
||||
|
||||
// Implementinf parameters
|
||||
// Abstractioning of 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() ?? 'en'}?->name ?? 'en',
|
||||
'language' => language::{$registration->getLanguageCode()}->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->name . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
|
@ -0,0 +1,337 @@
|
|||
<?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 cart
|
||||
*
|
||||
* @uses reservation
|
||||
* @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 cart extends core implements document_interface, collection_interface
|
||||
{
|
||||
use document_trait;
|
||||
|
||||
/**
|
||||
* Name of the collection in ArangoDB
|
||||
*/
|
||||
final public const string COLLECTION = 'cart';
|
||||
|
||||
/**
|
||||
* Search for all products
|
||||
*
|
||||
* Search for all products in the cart
|
||||
*
|
||||
* @param language|null $language Language
|
||||
* @param currency|null $currency Currency
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return array|null Array with implementing objects of documents from ArangoDB, if found
|
||||
*/
|
||||
public function products(
|
||||
?language $language = language::en,
|
||||
?currency $currency = currency::usd,
|
||||
array &$errors = []
|
||||
): ?array {
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(product::COLLECTION, product::TYPE, errors: $errors)) {
|
||||
// Initialized collections
|
||||
|
||||
// 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 d = v WITH COUNT INTO amount
|
||||
RETURN {
|
||||
[d._id]: {
|
||||
amount,
|
||||
document: MERGE(d, {
|
||||
name: d.name.@language,
|
||||
description: d.description.@language,
|
||||
compatibility: d.compatibility.@language,
|
||||
brand: d.brand.@language,
|
||||
cost: d.cost.@currency
|
||||
})
|
||||
}
|
||||
}
|
||||
AQL,
|
||||
[
|
||||
'graph' => 'catalog',
|
||||
'cart' => $this->getId(),
|
||||
'collection' => product::COLLECTION,
|
||||
'language' => $language->name,
|
||||
'currency' => $currency->name
|
||||
],
|
||||
flat: true,
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
/*
|
||||
* МеНЯ ЭТО РАЗДРАЖАЕТ
|
||||
*/
|
||||
|
||||
$products = [];
|
||||
|
||||
foreach ($result ?? [] as $raw) {
|
||||
foreach ($raw as $key => $value) {
|
||||
$products[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
} 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for summary of all products
|
||||
*
|
||||
* Search for summary of all products in the cart
|
||||
*
|
||||
* @param currency|null $currency Currency
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return array|null Array with implementing objects of documents from ArangoDB, if found
|
||||
*/
|
||||
public function summary(
|
||||
?currency $currency = currency::usd,
|
||||
array &$errors = []
|
||||
): ?array {
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(product::COLLECTION, product::TYPE, errors: $errors)) {
|
||||
// Initialized collections
|
||||
|
||||
// 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,
|
||||
[
|
||||
'graph' => 'catalog',
|
||||
'cart' => $this->getId(),
|
||||
'collection' => product::COLLECTION,
|
||||
'currency' => $currency->name
|
||||
],
|
||||
flat: true,
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
// 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);
|
||||
} 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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Count
|
||||
*
|
||||
* Count of the product in the cart
|
||||
*
|
||||
* @param product $product The product
|
||||
* @param int $limit Limit for counting
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return int|null Amount of the product in the cart, if counted
|
||||
*/
|
||||
public function count(product $product, int $limit = 100, array &$errors = []): ?int
|
||||
{
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(product::COLLECTION, product::TYPE, errors: $errors)) {
|
||||
// Initialized collections
|
||||
|
||||
// Search for the products in the cart and count them
|
||||
return (int) collection::execute(
|
||||
<<<AQL
|
||||
FOR v IN 1..1 INBOUND @cart GRAPH @graph
|
||||
FILTER IS_SAME_COLLECTION(@collection, v._id) && v._id == @product
|
||||
LIMIT @limit
|
||||
COLLECT WITH COUNT INTO length
|
||||
RETURN length
|
||||
AQL,
|
||||
[
|
||||
'graph' => 'catalog',
|
||||
'cart' => $this->getId(),
|
||||
'collection' => product::COLLECTION,
|
||||
'product' => $product->getId(),
|
||||
'limit' => $limit
|
||||
],
|
||||
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);
|
||||
} 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write
|
||||
*
|
||||
* Write the product in the cart
|
||||
*
|
||||
* @param product $product The product
|
||||
* @param int $amount Amount of writings
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function write(product $product, int $amount = 1, array &$errors = []): void
|
||||
{
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(product::COLLECTION, product::TYPE, errors: $errors)) {
|
||||
// Initialized collections
|
||||
|
||||
// Writing the product to the cart
|
||||
collection::execute(
|
||||
<<<AQL
|
||||
FOR i IN 1..@amount
|
||||
INSERT { _from: @product, _to: @cart, created: @created, updated: @created, active: true } INTO @@edge
|
||||
AQL,
|
||||
[
|
||||
'cart' => $this->getId(),
|
||||
'product' => $product->getId(),
|
||||
'@edge' => reservation::COLLECTION,
|
||||
'amount' => $amount,
|
||||
'created' => time()
|
||||
],
|
||||
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);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete
|
||||
*
|
||||
* Delete the product from the cart
|
||||
*
|
||||
* @param product $product The product
|
||||
* @param int $amount Amount of deletings
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete(product $product, int $amount = 1, array &$errors = []): void
|
||||
{
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(product::COLLECTION, product::TYPE, errors: $errors)) {
|
||||
// Initialized collections
|
||||
|
||||
// Deleting the product from the cart
|
||||
collection::execute(
|
||||
<<<AQL
|
||||
FOR v, e IN 1..1 INBOUND @cart GRAPH @graph
|
||||
FILTER IS_SAME_COLLECTION(@collection, v._id) && v._id == @product
|
||||
LIMIT @amount
|
||||
REMOVE e._key IN @@reservation
|
||||
AQL,
|
||||
[
|
||||
'graph' => 'catalog',
|
||||
'cart' => $this->getId(),
|
||||
'collection' => product::COLLECTION,
|
||||
'product' => $product->getId(),
|
||||
'@reservation' => reservation::COLLECTION,
|
||||
'amount' => $amount
|
||||
],
|
||||
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);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,20 +2,17 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models;
|
||||
namespace mirzaev\arming_bot\models;
|
||||
|
||||
// Files of the project
|
||||
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;
|
||||
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;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
|
@ -27,13 +24,10 @@ use avadim\FastExcelReader\Excel as excel;
|
|||
// Built-in libraries
|
||||
use exception;
|
||||
|
||||
// GD library
|
||||
use GdImage as image;
|
||||
|
||||
/**
|
||||
* Model of the catalog
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
* @package mirzaev\arming_bot\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
|
@ -41,64 +35,7 @@ use GdImage as image;
|
|||
final class catalog extends core
|
||||
{
|
||||
use yandex, files {
|
||||
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];
|
||||
yandex::download as yandex;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -177,7 +114,7 @@ final class catalog extends core
|
|||
// Iterate over categories
|
||||
|
||||
try {
|
||||
if (!empty($row['identifier']) && is_int($row['identifier']) && $row['identifier'] > 0 && !empty($row['name'])) {
|
||||
if (!empty($row['identifier']) && !empty($row['name'])) {
|
||||
// Required cells are filled in
|
||||
|
||||
// Incrementing the counter of loaded categories
|
||||
|
@ -206,19 +143,10 @@ final class catalog extends core
|
|||
// Not initialized the category
|
||||
|
||||
// Creating the category
|
||||
$_id = $created = category::write(
|
||||
identifier: (int) $row['identifier'],
|
||||
name: [$language->name => $row['name']],
|
||||
position: (int) $row['position'] ?? null,
|
||||
errors: $errors
|
||||
);
|
||||
$_id = $created = category::write((int) $row['identifier'], [$language->name => $row['name']], $row['position'] ?? null, $errors);
|
||||
|
||||
// Initializing the category
|
||||
$category = category::_read(
|
||||
filter: 'd._id == @_id',
|
||||
parameters: ['_id' => $_id],
|
||||
errors: $errors
|
||||
);
|
||||
$category = category::_read('d._id == @_id', parameters: ['_id' => $_id], errors: $errors);
|
||||
|
||||
// Incrementing the counter of created categories
|
||||
if ($created) ++$categories_created;
|
||||
|
@ -231,11 +159,7 @@ final class catalog extends core
|
|||
// Received the ascendant category
|
||||
|
||||
// Initializing the ascendant category
|
||||
$ascendant = category::_read(
|
||||
filter: 'd.identifier == @identifier',
|
||||
parameters: ['identifier' => (int) $row['category']],
|
||||
errors: $errors
|
||||
);
|
||||
$ascendant = category::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $row['category']], errors: $errors);
|
||||
|
||||
if ($ascendant instanceof category) {
|
||||
// Found the ascendant category
|
||||
|
@ -252,17 +176,13 @@ final class catalog extends core
|
|||
// Received images
|
||||
|
||||
// Initializing new images of the category
|
||||
$images = static::folder(
|
||||
uri: explode(' ', mb_trim($row['images']))[0],
|
||||
errors: $errors
|
||||
);
|
||||
$images = explode(' ', trim($row['images']));
|
||||
|
||||
// Reinitialize images? (true, if no images found or their amount does not match)
|
||||
/* $reinitialize = !$category->images || count($category->images) !== count($images); */
|
||||
$reinitialize = true;
|
||||
$reinitialize = !$category->images || count($category->images) !== count($images);
|
||||
|
||||
// Checking the identity of existing images with new images (if reinitialization of images has not yet been requested)
|
||||
/* if (!$reinitialize) foreach ($category->images as $key => $image) if ($reinitialize = $image['source'] !== $images[$key]) break; */
|
||||
if (!$reinitialize) foreach ($category->images as $key => $image) if ($reinitialize = $image['source'] !== $images[$key]) break;
|
||||
|
||||
if ($reinitialize) {
|
||||
// Requested reinitialization of images
|
||||
|
@ -270,98 +190,35 @@ final class catalog extends core
|
|||
// Initializing the buffer of images
|
||||
$buffer = [];
|
||||
|
||||
foreach (is_array($images) ? $images : [] as $image) {
|
||||
foreach ($images as $index => $image) {
|
||||
// Iterating over new images
|
||||
|
||||
// Initializing identifier of the image
|
||||
$identifier = preg_replace('/\.\w+$/', '', $image->name);
|
||||
// Skipping empty URI`s
|
||||
if (empty($image = trim($image))) continue;
|
||||
|
||||
// Initializing path to directory of images in storage
|
||||
$directory = DIRECTORY_SEPARATOR . 'categories' . DIRECTORY_SEPARATOR . $row['identifier'] . DIRECTORY_SEPARATOR . $identifier;
|
||||
// Initializing path to directory of the image in storage
|
||||
$directory = DIRECTORY_SEPARATOR . 'categories' . DIRECTORY_SEPARATOR . $row['identifier'];
|
||||
|
||||
// Initializing URL of the image in storage
|
||||
$url = STORAGE . $directory;
|
||||
|
||||
// Initializing URN of the image in storage
|
||||
$urn = $index . '.jpg';
|
||||
|
||||
// Initializing URI of the image in storage
|
||||
$uri = $url . DIRECTORY_SEPARATOR . $urn;
|
||||
|
||||
// Initializing the directory in storage
|
||||
if (!file_exists($url)) mkdir($url, 0775, true);
|
||||
|
||||
if ($downloaded = static::file(
|
||||
uri: $image->public_url ?? $image->public_key,
|
||||
destination: $url,
|
||||
name: 'source',
|
||||
errors: $errors
|
||||
)) {
|
||||
// The image is downloaded and initialized data of the image in storage
|
||||
if (static::yandex($image, $uri, errors: $errors)) {
|
||||
// The image is downloaded
|
||||
|
||||
// Initializing URI of the image
|
||||
$uri = $downloaded['destination'] . DIRECTORY_SEPARATOR . $downloaded['name'] . '.' . $downloaded['content']->extension();
|
||||
|
||||
// Initializing size of the image
|
||||
$size = getimagesize($uri);
|
||||
|
||||
if ($downloaded['content'] === content::png) {
|
||||
// PNG
|
||||
|
||||
// Initializing implementator of the image
|
||||
$boba = imagecreatefrompng($uri);
|
||||
} else if ($downloaded['content'] === content::jpeg) {
|
||||
// JPEG
|
||||
|
||||
// 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
|
||||
imageantialias($boba, true);
|
||||
|
||||
if ($boba instanceof image) {
|
||||
// Initialized implementator of the image
|
||||
|
||||
// Initializing buffer of resized images
|
||||
$resized = [];
|
||||
|
||||
foreach ([1400, 800, 400, 200] as $resize) {
|
||||
// Iterating over sizes for creating images
|
||||
|
||||
if ($size[0] >= $size[1]) {
|
||||
// The width ($size[0]) is longer than the height ($size[1])
|
||||
|
||||
$width = $resize;
|
||||
$height = (int) ($resize * $size[1] / $size[0]);
|
||||
} else {
|
||||
// The height ($size[1]) is longer than the width ($size[0])
|
||||
|
||||
$width = (int) ($resize * $size[0] / $size[1]);
|
||||
$height = $resize;
|
||||
}
|
||||
|
||||
// Resizing the image
|
||||
$biba = imagecreatetruecolor($width, $height);
|
||||
imagecopyresampled($biba, $boba, 0, 0, 0, 0, $width, $height, $size[0], $size[1]);
|
||||
|
||||
// Initializing URI of the resized image
|
||||
$uri = $directory . DIRECTORY_SEPARATOR . "$resize.webp";
|
||||
|
||||
// Saving the image
|
||||
imagewebp($biba, STORAGE . $uri);
|
||||
|
||||
// Writing the resized image to the buffer of resized images
|
||||
$resized[$resize] = $uri;
|
||||
}
|
||||
|
||||
// Writing the image to the buffer if images
|
||||
$buffer[] = [
|
||||
'source' => $image->public_url ?? null,
|
||||
'storage' => [
|
||||
'source' => $directory . DIRECTORY_SEPARATOR . $downloaded['name'] . '.' . $downloaded['content']->extension(),
|
||||
] + $resized
|
||||
];
|
||||
}
|
||||
// Writing the image to the buffer if images
|
||||
$buffer[] = [
|
||||
'source' => $image,
|
||||
'storage' => $directory . DIRECTORY_SEPARATOR . $urn
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -375,7 +232,7 @@ final class catalog extends core
|
|||
|
||||
// Incrementing the counter of updated categories
|
||||
if ($updated && !$created) ++$categories_updated;
|
||||
} else throw new exception('Failed to initialize category: ' . $row['name'] . " ($number)");
|
||||
} else throw new exception("Failed to initialize category: {$row['name']} ($number)");
|
||||
}
|
||||
|
||||
// Writing to the registry of handled categories and products
|
||||
|
@ -414,7 +271,7 @@ final class catalog extends core
|
|||
// Iterate over products
|
||||
|
||||
try {
|
||||
if (!empty($row['identifier']) && is_int($row['identifier']) && $row['identifier'] > 0 && !empty($row['name'])) {
|
||||
if (!empty($row['identifier']) && !empty($row['name'])) {
|
||||
// Required cells are filled in
|
||||
|
||||
// Incrementing the counter of loaded products
|
||||
|
@ -450,21 +307,21 @@ final class catalog extends core
|
|||
|
||||
// Initializing position of the product
|
||||
if (empty($product->position) || $product->position !== $row['position'])
|
||||
$product->position = isset($row['position']) ? (int) $row['position'] : 0;
|
||||
$product->position = $row['position'];
|
||||
} else {
|
||||
// Not initialized the product
|
||||
|
||||
// Creating the product
|
||||
$_id = product::write(
|
||||
identifier: (int) $row['identifier'],
|
||||
name: [$language->name => $row['name']],
|
||||
description: [$language->name => $row['description']],
|
||||
cost: [$currency->name => (float) $row['cost']],
|
||||
weight: (float) $row['weight'],
|
||||
dimensions: ['x' => $row['x'], 'y' => $row['y'], 'z' => $row['z']],
|
||||
brand: [$language->name => $row['brand']],
|
||||
compatibility: [$language->name => $row['compatibility']],
|
||||
position: isset($row['position']) ? (int) $row['position'] : 0,
|
||||
(int) $row['identifier'],
|
||||
[$language->name => $row['name']],
|
||||
[$language->name => $row['description']],
|
||||
[$currency->name => (float) $row['cost']],
|
||||
(float) $row['weight'],
|
||||
['x' => $row['x'], 'y' => $row['y'], 'z' => $row['z']],
|
||||
[$language->name => $row['brand']],
|
||||
[$language->name => $row['compatibility']],
|
||||
$row['position'] ?? null,
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
|
@ -482,10 +339,7 @@ final class catalog extends core
|
|||
// Received the category
|
||||
|
||||
// Initializing the category
|
||||
$category = category::_read(
|
||||
filter: sprintf('d.identifier == %u', (int) $row['category']),
|
||||
errors: $errors
|
||||
);
|
||||
$category = category::_read(sprintf('d.identifier == %u', (int) $row['category']), errors: $errors);
|
||||
|
||||
if ($category instanceof category) {
|
||||
// Found the ascendant category
|
||||
|
@ -501,18 +355,14 @@ final class catalog extends core
|
|||
if (!empty($row['images'])) {
|
||||
// Received images
|
||||
|
||||
// Initializing new images of the product
|
||||
$images = static::folder(
|
||||
uri: explode(' ', mb_trim($row['images']))[0],
|
||||
errors: $errors
|
||||
);
|
||||
// Initializing new images of the category
|
||||
$images = explode(' ', trim($row['images']));
|
||||
|
||||
// Reinitialize images? (true, if no images found or their amount does not match)
|
||||
/* $reinitialize = !$product->images || count($product->images) !== count($images); */
|
||||
$reinitialize = true;
|
||||
$reinitialize = !$product->images || count($product->images) !== count($images);
|
||||
|
||||
// Checking the identity of existing images with new images (if reinitialization of images has not yet been requested)
|
||||
/* if (!$reinitialize) foreach ($product->images as $key => $image) if ($reinitialize = $image['source'] !== $images[$key]) break; */
|
||||
if (!$reinitialize) foreach ($product->images as $key => $image) if ($reinitialize = $image['source'] !== $images[$key]) break;
|
||||
|
||||
if ($reinitialize) {
|
||||
// Requested reinitialization of images
|
||||
|
@ -520,102 +370,39 @@ final class catalog extends core
|
|||
// Initializing the buffer of images
|
||||
$buffer = [];
|
||||
|
||||
foreach (is_array($images) ? $images : [] as $image) {
|
||||
foreach ($images as $index => $image) {
|
||||
// Iterating over new images
|
||||
|
||||
// Initializing identifier of the image
|
||||
$identifier = preg_replace('/\.\w+$/', '', $image->name);
|
||||
// Skipping empty URI`s
|
||||
if (empty($image = trim($image))) continue;
|
||||
|
||||
// Initializing path to directory of images in storage
|
||||
$directory = DIRECTORY_SEPARATOR . 'products' . DIRECTORY_SEPARATOR . $row['identifier'] . DIRECTORY_SEPARATOR . $identifier;
|
||||
// Initializing path to directory of the image in storage
|
||||
$directory = DIRECTORY_SEPARATOR . 'products' . DIRECTORY_SEPARATOR . $row['identifier'];
|
||||
|
||||
// Initializing URL of the image in storage
|
||||
$url = STORAGE . $directory;
|
||||
|
||||
// Initializing URN of the image in storage
|
||||
$urn = $index . '.jpg';
|
||||
|
||||
// Initializing URI of the image in storage
|
||||
$uri = $url . DIRECTORY_SEPARATOR . $urn;
|
||||
|
||||
// Initializing the directory in storage
|
||||
if (!file_exists($url)) mkdir($url, 0775, true);
|
||||
|
||||
if ($downloaded = static::file(
|
||||
uri: $image->public_url ?? $image->public_key,
|
||||
destination: $url,
|
||||
name: 'source',
|
||||
errors: $errors
|
||||
)) {
|
||||
// The image is downloaded and initialized data of the image in storage
|
||||
if (static::yandex($image, $uri, errors: $errors)) {
|
||||
// The image is downloaded
|
||||
|
||||
// Initializing URI of the image
|
||||
$uri = $downloaded['destination'] . DIRECTORY_SEPARATOR . $downloaded['name'] . '.' . $downloaded['content']->extension();
|
||||
|
||||
// Initializing size of the image
|
||||
$size = getimagesize($uri);
|
||||
|
||||
if ($downloaded['content'] === content::png) {
|
||||
// PNG
|
||||
|
||||
// Initializing implementator of the image
|
||||
$boba = imagecreatefrompng($uri);
|
||||
} else if ($downloaded['content'] === content::jpeg) {
|
||||
// JPEG
|
||||
|
||||
// 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
|
||||
imageantialias($boba, true);
|
||||
|
||||
if ($boba instanceof image) {
|
||||
// Initialized implementator of the image
|
||||
|
||||
// Initializing buffer of resized images
|
||||
$resized = [];
|
||||
|
||||
foreach ([1400, 800, 400, 200] as $resize) {
|
||||
// Iterating over sizes for creating images
|
||||
|
||||
if ($size[0] >= $size[1]) {
|
||||
// The width ($size[0]) is longer than the height ($size[1])
|
||||
|
||||
$width = $resize;
|
||||
$height = (int) ($resize * $size[1] / $size[0]);
|
||||
} else {
|
||||
// The height ($size[1]) is longer than the width ($size[0])
|
||||
|
||||
$width = (int) ($resize * $size[0] / $size[1]);
|
||||
$height = $resize;
|
||||
}
|
||||
|
||||
// Resizing the image
|
||||
$biba = imagecreatetruecolor($width, $height);
|
||||
imagecopyresampled($biba, $boba, 0, 0, 0, 0, $width, $height, $size[0], $size[1]);
|
||||
|
||||
// Initializing URI of the resized image
|
||||
$uri = $directory . DIRECTORY_SEPARATOR . "$resize.webp";
|
||||
|
||||
// Saving the image
|
||||
imagewebp($biba, STORAGE . $uri);
|
||||
|
||||
// Writing the resized image to the buffer of resized images
|
||||
$resized[$resize] = $uri;
|
||||
}
|
||||
|
||||
// Writing the image to the buffer if images
|
||||
$buffer[] = [
|
||||
'source' => $image->public_url ?? null,
|
||||
'storage' => [
|
||||
'source' => $directory . DIRECTORY_SEPARATOR . $downloaded['name'] . '.' . $downloaded['content']->extension(),
|
||||
] + $resized
|
||||
];
|
||||
}
|
||||
// Writing the image to the buffer if images
|
||||
$buffer[] = [
|
||||
'source' => $image,
|
||||
'storage' => $directory . DIRECTORY_SEPARATOR . $urn
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Initializing images of the product
|
||||
// Initializing images of the category
|
||||
$product->images = $buffer;
|
||||
}
|
||||
}
|
||||
|
@ -623,9 +410,9 @@ final class catalog extends core
|
|||
// Writing in ArangoDB
|
||||
$updated = document::update($product->__document(), errors: $errors);
|
||||
|
||||
// Incrementing the counter of updated products
|
||||
// Incrementing the counter of updated categories
|
||||
if ($updated && !$created) ++$products_updated;
|
||||
} else throw new exception('Failed to initialize product: ' . $row['name'] . " ($number)");
|
||||
} else throw new exception("Failed to initialize product: {$row['name']} ($number)");
|
||||
}
|
||||
|
||||
// Writing to the registry of handled categories and products
|
||||
|
@ -659,25 +446,16 @@ final class catalog extends core
|
|||
$category instanceof category
|
||||
&& array_search($category->identifier, $handled['categories']) === false
|
||||
) {
|
||||
// Not found identifier of the category in the buffer of handled categories and products
|
||||
// Not found identifier of the product in the buffer of handled categories and products
|
||||
|
||||
// Deleting images of the category from storage
|
||||
static::delete(
|
||||
directory: STORAGE . DIRECTORY_SEPARATOR . 'categories' . DIRECTORY_SEPARATOR . $category->identifier,
|
||||
errors: $errors
|
||||
);
|
||||
static::delete(STORAGE . DIRECTORY_SEPARATOR . 'categories' . DIRECTORY_SEPARATOR . $category->identifier, errors: $errors);
|
||||
|
||||
// Deleting entries of the category in ArangoDB
|
||||
entry::banish(
|
||||
document: $category,
|
||||
errors: $errors
|
||||
);
|
||||
entry::banish($category, errors: $errors);
|
||||
|
||||
// Deleting the category in ArangoDB
|
||||
document::delete(
|
||||
document: $category->__document(),
|
||||
errors: $errors
|
||||
);
|
||||
document::delete($category->__document(), errors: $errors);
|
||||
|
||||
// Incrementing the counter of deleted categories
|
||||
++$categories_deleted;
|
||||
|
@ -705,22 +483,13 @@ final class catalog extends core
|
|||
// Not found identifier of the product in the buffer of handled categories and products
|
||||
|
||||
// Deleting images of the product from storage
|
||||
static::delete(
|
||||
directory: STORAGE . DIRECTORY_SEPARATOR . 'products' . DIRECTORY_SEPARATOR . $product->identifier,
|
||||
errors: $errors
|
||||
);
|
||||
static::delete(STORAGE . DIRECTORY_SEPARATOR . 'products' . DIRECTORY_SEPARATOR . $product->identifier, errors: $errors);
|
||||
|
||||
// Deleting entries of the product in ArangoDB
|
||||
entry::banish(
|
||||
document: $product,
|
||||
errors: $errors
|
||||
);
|
||||
entry::banish($product, errors: $errors);
|
||||
|
||||
// Deleting the product in ArangoDB
|
||||
document::delete(
|
||||
document: $product->__document(),
|
||||
errors: $errors
|
||||
);
|
||||
document::delete($product->__document(), errors: $errors);
|
||||
|
||||
// Incrementing the counter of deleted products
|
||||
++$products_deleted;
|
||||
|
@ -728,8 +497,8 @@ final class catalog extends core
|
|||
}
|
||||
|
||||
// Counting new documents
|
||||
$categories_new = collection::count(collection: category::COLLECTION, errors: $errors);
|
||||
$products_new = collection::count(collection: product::COLLECTION, errors: $errors);
|
||||
$categories_new = collection::count(category::COLLECTION, errors: $errors);
|
||||
$products_new = collection::count(product::COLLECTION, errors: $errors);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models;
|
||||
namespace mirzaev\arming_bot\models;
|
||||
|
||||
// Files of the project
|
||||
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;
|
||||
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;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
|
@ -21,7 +21,7 @@ use exception;
|
|||
/**
|
||||
* Model of category
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
* @package mirzaev\arming_bot\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
|
@ -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->name . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models;
|
||||
namespace mirzaev\arming_bot\models;
|
||||
|
||||
// Files of the project
|
||||
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;
|
||||
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;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\enumerations\collection\type;
|
||||
|
@ -16,7 +16,7 @@ use mirzaev\arangodb\enumerations\collection\type;
|
|||
/**
|
||||
* Model of connect
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
* @package mirzaev\arming_bot\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models;
|
||||
namespace mirzaev\arming_bot\models;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\model;
|
||||
|
@ -20,13 +20,18 @@ use exception;
|
|||
/**
|
||||
* Core of models
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
* @package mirzaev\arming_bot\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
class core extends model
|
||||
{
|
||||
/**
|
||||
* Postfix for name of models files
|
||||
*/
|
||||
final public const string POSTFIX = '';
|
||||
|
||||
/**
|
||||
* Path to the file with settings of connecting to the ArangoDB
|
||||
*/
|
||||
|
@ -42,7 +47,7 @@ class core extends model
|
|||
/**
|
||||
* Constructor of an instance
|
||||
*
|
||||
* @param bool $initialize Initialize ...?
|
||||
* @param bool $initialize Initialize a model?
|
||||
* @param ?arangodb $arangodb Instance of a session of ArangoDB
|
||||
*
|
||||
* @return void
|
||||
|
@ -127,7 +132,7 @@ class core extends model
|
|||
|
||||
// Exit (success)
|
||||
return $result;
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to registry of errors
|
||||
$errors[] = [
|
||||
|
@ -142,6 +147,7 @@ class core extends model
|
|||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write
|
||||
*
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\arming_bot\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;
|
||||
|
||||
// The HTTP PSR-18 adapter for Guzzle HTTP client
|
||||
use Http\Adapter\Guzzle7\Client as guzzle;
|
||||
|
||||
// Framework for CDEK
|
||||
use CdekSDK2\Client as client,
|
||||
CdekSDK2\Dto\CityList as cities;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Model of CDEK
|
||||
*
|
||||
* @package mirzaev\arming_bot\models\deliveries
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class cdek extends core implements document_interface, collection_interface
|
||||
{
|
||||
use document_trait;
|
||||
|
||||
/**
|
||||
* Name of the collection in ArangoDB
|
||||
*/
|
||||
final public const string COLLECTION = 'delivery';
|
||||
|
||||
/**
|
||||
* Calculate
|
||||
*
|
||||
* Calculate delivery by CDEK
|
||||
*
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static function calculate(array &$errors = []): static|null
|
||||
{
|
||||
try {
|
||||
//
|
||||
/* $client = new client(new guzzle, 'account', 'secure'); */
|
||||
$client = new client(new guzzle);
|
||||
$client->setTest(true);
|
||||
|
||||
$result = $client->cities()->getFiltered(['country_codes' => 'RU', 'city' => 'зеленогорск']);
|
||||
|
||||
if ($result->isOk()) {
|
||||
//
|
||||
|
||||
//Запрос успешно выполнился
|
||||
$cities = $client->formatResponseList($result, cities::class);
|
||||
|
||||
foreach ($cities->items as $city) {
|
||||
var_dump($city);
|
||||
}
|
||||
|
||||
die;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models;
|
||||
namespace mirzaev\arming_bot\models;
|
||||
|
||||
// Files of the project
|
||||
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;
|
||||
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;
|
||||
|
||||
// Library for ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
@ -24,7 +24,7 @@ use exception;
|
|||
/**
|
||||
* Model of entry
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
* @package mirzaev\arming_bot\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
|
@ -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->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);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . $to::TYPE . ' collection: ' . $to::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . $from::TYPE . ' collection: ' . $from::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
|
@ -111,21 +111,16 @@ final class entry extends core implements document_interface, collection_interfa
|
|||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// Initialized the collection
|
||||
|
||||
// Search for ascendants
|
||||
|
||||
// Search for ascendants
|
||||
if ($result = collection::execute(
|
||||
sprintf(
|
||||
<<<'AQL'
|
||||
let from = (
|
||||
FOR e IN @@edge
|
||||
RETURN DISTINCT e._from
|
||||
)
|
||||
|
||||
FOR d in @@collection
|
||||
FILTER !POSITION(from, d._id)
|
||||
RETURN %s
|
||||
FOR d IN @@collection
|
||||
FOR ascendant IN OUTBOUND d @@edge
|
||||
RETURN %s
|
||||
AQL,
|
||||
empty($return) ? 'DISTINCT d' : $return
|
||||
empty($return) ? 'DISTINCT ascendant' : $return
|
||||
),
|
||||
[
|
||||
'@collection' => $descendant::COLLECTION,
|
||||
|
@ -138,7 +133,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->name . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
|
@ -194,9 +189,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->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);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . $to::TYPE . ' collection: ' . $to::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . $from::TYPE . ' collection: ' . $from::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
|
@ -223,9 +218,8 @@ 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 $depth Amount of nodes for traversal search (subcategories/products inside subcategories...)
|
||||
* @param int $page Page
|
||||
* @param int $amount Amount of documents per page
|
||||
* @param int $page Страница
|
||||
* @param int $amount Количество товаров на странице
|
||||
* @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]
|
||||
|
@ -237,7 +231,6 @@ 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,
|
||||
|
@ -254,13 +247,12 @@ final class entry extends core implements document_interface, collection_interfa
|
|||
return is_array($result = collection::execute(
|
||||
sprintf(
|
||||
<<<'AQL'
|
||||
FOR v IN 1..%u INBOUND @document GRAPH @graph
|
||||
FOR v IN 1..1 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",
|
||||
|
@ -276,8 +268,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->name . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . $document::TYPE->name . ' collection: ' . $document::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . $document::TYPE . ' collection: ' . $document::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
|
@ -323,8 +315,8 @@ final class entry extends core implements document_interface, collection_interfa
|
|||
],
|
||||
errors: $errors
|
||||
);
|
||||
} 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);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . $document::TYPE . ' collection: ' . $document::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
6
mirzaev/huesos/system/models/enumerations/currency.php → mirzaev/arming_bot/system/models/enumerations/currency.php
Executable file → Normal file
6
mirzaev/huesos/system/models/enumerations/currency.php → mirzaev/arming_bot/system/models/enumerations/currency.php
Executable file → Normal file
|
@ -2,15 +2,15 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\enumerations;
|
||||
namespace mirzaev\arming_bot\models\enumerations;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\models\enumerations\language;
|
||||
use mirzaev\arming_bot\models\enumerations\language;
|
||||
|
||||
/**
|
||||
* Types of currencies by ISO 4217 standart
|
||||
*
|
||||
* @package mirzaev\huesos\models\enumerations
|
||||
* @package mirzaev\arming_bot\models\enumerations
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
4
mirzaev/huesos/system/models/enumerations/language.php → mirzaev/arming_bot/system/models/enumerations/language.php
Executable file → Normal file
4
mirzaev/huesos/system/models/enumerations/language.php → mirzaev/arming_bot/system/models/enumerations/language.php
Executable file → Normal file
|
@ -2,12 +2,12 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\enumerations;
|
||||
namespace mirzaev\arming_bot\models\enumerations;
|
||||
|
||||
/**
|
||||
* Types of languages by ISO 639-1 standart
|
||||
*
|
||||
* @package mirzaev\huesos\models\enumerations
|
||||
* @package mirzaev\arming_bot\models\enumerations
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
4
mirzaev/huesos/system/models/enumerations/session.php → mirzaev/arming_bot/system/models/enumerations/session.php
Executable file → Normal file
4
mirzaev/huesos/system/models/enumerations/session.php → mirzaev/arming_bot/system/models/enumerations/session.php
Executable file → Normal file
|
@ -2,12 +2,12 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\enumerations;
|
||||
namespace mirzaev\arming_bot\models\enumerations;
|
||||
|
||||
/**
|
||||
* Types of session verification
|
||||
*
|
||||
* @package mirzaev\huesos\models\enumerations
|
||||
* @package mirzaev\arming_bot\models\enumerations
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\interfaces;
|
||||
namespace mirzaev\arming_bot\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\huesos\models\traits
|
||||
* @package mirzaev\arming_bot\models\traits
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\interfaces;
|
||||
namespace mirzaev\arming_bot\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\huesos\models\traits
|
||||
* @package mirzaev\arming_bot\models\traits
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
|
@ -2,18 +2,18 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models;
|
||||
namespace mirzaev\arming_bot\models;
|
||||
|
||||
// Files of the project
|
||||
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;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Model of menu
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
* @package mirzaev\arming_bot\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
|
@ -2,15 +2,15 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models;
|
||||
namespace mirzaev\arming_bot\models;
|
||||
|
||||
// Files of the project
|
||||
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;
|
||||
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;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
|
@ -25,7 +25,7 @@ use exception;
|
|||
/**
|
||||
* Model of a product
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
* @package mirzaev\arming_bot\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
|
@ -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->name . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE . ' 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->name . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE . ' 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->name . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models;
|
||||
namespace mirzaev\arming_bot\models;
|
||||
|
||||
// Files of the project
|
||||
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;
|
||||
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;
|
||||
|
||||
// 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\huesos\models
|
||||
* @package mirzaev\arming_bot\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
|
@ -2,20 +2,20 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models;
|
||||
namespace mirzaev\arming_bot\models;
|
||||
|
||||
// Files of the project
|
||||
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;
|
||||
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;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
|
@ -30,7 +30,7 @@ use exception;
|
|||
/**
|
||||
* Model of a session
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
* @package mirzaev\arming_bot\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
|
@ -53,15 +53,15 @@ final class session extends core implements document_interface, collection_inter
|
|||
final public const verification VERIFICATION = verification::hash_else_address;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* Constructor of instance
|
||||
*
|
||||
* Initialize session and write into the $this->document property
|
||||
* Initialize of a session and write them to the $this->document property
|
||||
*
|
||||
* @param ?string $hash Hash of the session in ArangoDB
|
||||
* @param ?int $expires Date of expiring of the session (used for creating a new session)
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return void
|
||||
* @return static
|
||||
*/
|
||||
public function __construct(?string $hash = null, ?int $expires = null, array &$errors = [])
|
||||
{
|
||||
|
@ -71,7 +71,7 @@ final class session extends core implements document_interface, collection_inter
|
|||
|
||||
if (isset($hash) && $document = $this->hash($hash, errors: $errors)) {
|
||||
// Found the instance of the ArangoDB document of session and received a session hash
|
||||
|
||||
|
||||
// Writing document instance of the session from ArangoDB to the property of the implementing object
|
||||
$this->__document($document);
|
||||
} else if (static::VERIFICATION === verification::hash_else_address && $document = $this->address($_SERVER['REMOTE_ADDR'], errors: $errors)) {
|
||||
|
@ -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 into ArangoDB
|
||||
// Update is writed to 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->name . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE . ' 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->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);
|
||||
} else throw new exception('Failed to initialize ' . account::TYPE . ' collection: ' . account::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . connect::TYPE . ' collection: ' . connect::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
|
@ -222,7 +222,7 @@ final class session extends core implements document_interface, collection_inter
|
|||
return collection::execute(
|
||||
<<<'AQL'
|
||||
FOR d IN @@collection
|
||||
FILTER d.hash == @hash && d.expires > @time && d.active == true
|
||||
FILTER d.hash == @hash && d.expires > $time && d.active == true
|
||||
RETURN d
|
||||
AQL,
|
||||
[
|
||||
|
@ -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->name . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
|
@ -268,18 +268,16 @@ final class session extends core implements document_interface, collection_inter
|
|||
<<<'AQL'
|
||||
FOR d IN @@collection
|
||||
FILTER d.address == @address && d.expires > @time && d.active == true
|
||||
SORT d.updated DESC
|
||||
LIMIT 1
|
||||
RETURN d
|
||||
AQL,
|
||||
[
|
||||
'@collection' => static::COLLECTION,
|
||||
'@collection' => static::COLLECTION,
|
||||
'address' => $address,
|
||||
'time' => time()
|
||||
],
|
||||
errors: $errors
|
||||
);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
|
@ -2,15 +2,15 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models;
|
||||
namespace mirzaev\arming_bot\models;
|
||||
|
||||
// Files of the project
|
||||
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;
|
||||
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;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
|
@ -25,7 +25,7 @@ use exception;
|
|||
/**
|
||||
* Model of settings
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
* @package mirzaev\arming_bot\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
|
@ -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->name . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models;
|
||||
namespace mirzaev\arming_bot\models;
|
||||
|
||||
// Files of the project
|
||||
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;
|
||||
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;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
|
@ -25,7 +25,7 @@ use exception,
|
|||
/**
|
||||
* Model of a suspension
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
* @package mirzaev\arming_bot\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
|
@ -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->name . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
|
@ -0,0 +1,586 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\arming_bot\models;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\arming_bot\models\core,
|
||||
mirzaev\arming_bot\controllers\core as controller,
|
||||
mirzaev\arming_bot\models\catalog,
|
||||
mirzaev\arming_bot\models\suspension,
|
||||
mirzaev\arming_bot\models\account;
|
||||
|
||||
// Framework for Telegram
|
||||
use Zanzara\Zanzara,
|
||||
Zanzara\Context,
|
||||
Zanzara\Telegram\Type\Input\InputFile,
|
||||
Zanzara\Telegram\Type\File\Document as telegram_document,
|
||||
Zanzara\Middleware\MiddlewareNode as Node;
|
||||
|
||||
/**
|
||||
* Model of chat (telegram)
|
||||
*
|
||||
* @package mirzaev\arming_bot\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class telegram extends core
|
||||
{
|
||||
/**
|
||||
* Экранирование символов для Markdown
|
||||
*
|
||||
* @param string $text Текст для экранирования
|
||||
* @param array $exception Символы которые будут исключены из списка для экранирования
|
||||
*
|
||||
* @return string Экранированный текст
|
||||
*/
|
||||
public static function unmarkdown(string $text, array $exceptions = []): string
|
||||
{
|
||||
// Инициализация реестра символом для конвертации
|
||||
$from = array_diff(
|
||||
[
|
||||
'#',
|
||||
'*',
|
||||
'_',
|
||||
'=',
|
||||
'.',
|
||||
'[',
|
||||
']',
|
||||
'(',
|
||||
')',
|
||||
'-',
|
||||
'>',
|
||||
'<',
|
||||
'!',
|
||||
'`'
|
||||
],
|
||||
$exceptions
|
||||
);
|
||||
|
||||
// Инициализация реестра целей для конвертации
|
||||
$to = [];
|
||||
foreach ($from as $symbol) $to[] = "\\$symbol";
|
||||
|
||||
// Конвертация и выход (успех)
|
||||
return str_replace($from, $to, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Инициализация запчасти
|
||||
*
|
||||
* Проверяет существование запчасти
|
||||
*
|
||||
* @param string $spare Запчасть
|
||||
*
|
||||
* @return string|bool Запчасть, если найдена, иначе false
|
||||
*/
|
||||
public static function spares(string $spare): string|bool
|
||||
{
|
||||
// Поиск запчастей и выход (успех)
|
||||
return match (mb_strtolower($spare)) {
|
||||
'цевьё' => 'Цевьё',
|
||||
default => false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Главное меню
|
||||
*
|
||||
* Команда: /start
|
||||
*
|
||||
* @param Context $ctx
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function menu(Context $ctx): void
|
||||
{
|
||||
// Инициализация клавиатуры
|
||||
$keyboard = [
|
||||
[
|
||||
['text' => '🛒 Каталог', 'web_app' => ['url' => 'https://arming.dev.mirzaev.sexy']]
|
||||
],
|
||||
[
|
||||
['text' => '🏛️ О компании'],
|
||||
['text' => '💬 Контакты']
|
||||
],
|
||||
[
|
||||
['text' => '🎯 Сообщество']
|
||||
]
|
||||
];
|
||||
|
||||
if ($ctx->get('account')?->access['settings']) $keyboard[] = [['text' => '⚙️ Настройки']];
|
||||
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage(
|
||||
static::unmarkdown(<<<TXT
|
||||
Это сообщение будет отображаться (оно должно быть обязательно) при вызове главного меню командой /start (создаёт кнопки меню снизу)
|
||||
TXT),
|
||||
[
|
||||
'reply_markup' => [
|
||||
'keyboard' => $keyboard,
|
||||
'resize_keyboard' => true
|
||||
],
|
||||
'disable_notification' => true
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Начало работы с чат-роботом
|
||||
*
|
||||
* Команда: /start
|
||||
*
|
||||
* @param Context $ctx
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function start(Context $ctx): void
|
||||
{
|
||||
// Главное меню
|
||||
static::menu($ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Контакты
|
||||
*
|
||||
* Команда: /contacts
|
||||
*
|
||||
* @param Context $ctx
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function contacts(Context $ctx): void
|
||||
{
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage(static::unmarkdown(<<<TXT
|
||||
Здесь придумать текст для раздела "Контакты"
|
||||
TXT), [
|
||||
'reply_markup' => [
|
||||
'inline_keyboard' => [
|
||||
[
|
||||
['text' => '⚡ Связь с менеджером', 'url' => 'https://t.me/iarming'],
|
||||
],
|
||||
[
|
||||
['text' => '📨 Почта', 'callback_data' => 'mail']
|
||||
],
|
||||
[
|
||||
['text' => '🪖 Сайт', 'url' => 'https://arming.ru'],
|
||||
['text' => '🛒 Wildberries', 'url' => 'https://www.wildberries.ru/seller/137386'],
|
||||
['text' => '🛒 Ozon', 'url' => 'https://www.ozon.ru/seller/arming-1086587/products/?miniapp=seller_1086587'],
|
||||
]
|
||||
]
|
||||
],
|
||||
'link_preview_options' => [
|
||||
'is_disabled' => true
|
||||
],
|
||||
'disable_notification' => true
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Почта
|
||||
*
|
||||
* @param Context $ctx
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function _mail(Context $ctx): void
|
||||
{
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage(static::unmarkdown(<<<TXT
|
||||
[info@arming.ru](mailto::info@arming.ru)
|
||||
TXT, ['[', ']', '(', ')']), [
|
||||
'link_preview_options' => [
|
||||
'is_disabled' => true
|
||||
],
|
||||
'disable_notification' => true
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Компания
|
||||
*
|
||||
* Команда: /company
|
||||
*
|
||||
* @param Context $ctx
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function company(Context $ctx): void
|
||||
{
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage(
|
||||
static::unmarkdown(<<<TXT
|
||||
Здесь придумать текст для раздела "Компания"
|
||||
TXT),
|
||||
/* [
|
||||
'reply_markup' => [
|
||||
'inline_keyboard' => [
|
||||
[
|
||||
['text' => '⚡ Связь с менеджером', 'url' => 'https://git.mirzaev.sexy/mirzaev/mashtrash'],
|
||||
['text' => '📨 Почта', 'text' => ''],
|
||||
],
|
||||
[
|
||||
['text' => '🪖 Сайт', 'url' => '']
|
||||
['text' => '🛒 Wildberries', 'url' => '']
|
||||
]
|
||||
]
|
||||
],
|
||||
'link_preview_options' => [
|
||||
'is_disabled' => true
|
||||
]
|
||||
] */
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Сообщество
|
||||
*
|
||||
* Команда: /community
|
||||
*
|
||||
* @param Context $ctx
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function community(Context $ctx): void
|
||||
{
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage(static::unmarkdown(<<<TXT
|
||||
Здесь придумать текст для раздела "Сообщество"
|
||||
TXT), [
|
||||
'reply_markup' => [
|
||||
'inline_keyboard' => [
|
||||
[
|
||||
['text' => '💬 Основной чат', 'url' => 'https://t.me/arming_zone'],
|
||||
]
|
||||
]
|
||||
],
|
||||
'link_preview_options' => [
|
||||
'is_disabled' => true
|
||||
],
|
||||
'disable_notification' => true
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Настройки (доступ только авторизованным)
|
||||
*
|
||||
* Команда: /settings
|
||||
*
|
||||
* @param Context $ctx
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function settings(Context $ctx): void
|
||||
{
|
||||
if ($ctx->get('account')?->access['settings']) {
|
||||
// Авторизован доступ к настройкам
|
||||
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage(
|
||||
static::unmarkdown(<<<TXT
|
||||
Панель управления чат-роботом ARMING
|
||||
TXT),
|
||||
[
|
||||
'reply_markup' => [
|
||||
'inline_keyboard' => [
|
||||
[
|
||||
['text' => '📦 Импорт товаров', 'callback_data' => 'import_request'],
|
||||
]
|
||||
]
|
||||
],
|
||||
'link_preview_options' => [
|
||||
'is_disabled' => true
|
||||
],
|
||||
'disable_notification' => true
|
||||
]
|
||||
);
|
||||
} else {
|
||||
// Не авторизован доступ к настройкам
|
||||
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage('⛔ *Нет доступа*');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Запросить файл для импорта товаров (доступ только авторизованным)
|
||||
*
|
||||
* @param Context $ctx
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function import_request(Context $ctx): void
|
||||
{
|
||||
if ($ctx->get('account')?->access['settings']) {
|
||||
// Авторизован доступ к настройкам
|
||||
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage(static::unmarkdown('Отправьте документ в формате xlsx со списком товаров'))
|
||||
->then(function ($message) use ($ctx) {
|
||||
// Отправка файла
|
||||
$ctx->sendDocument(new InputFile(CATALOG_EXAMPLE), ['disable_notification' => true]);
|
||||
|
||||
// Импорт файла
|
||||
$ctx->nextStep([static::class, 'import'], true);
|
||||
});
|
||||
} else {
|
||||
// Не авторизован доступ к настройкам
|
||||
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage('⛔ *Нет доступа*');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Импорт товаров (доступ только авторизованным)
|
||||
*
|
||||
* @param Context $ctx
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function import(Context $ctx): void
|
||||
{
|
||||
if (($account = $ctx->get('account'))?->access['settings']) {
|
||||
// Авторизован доступ к настройкам
|
||||
|
||||
// Инициализация документа
|
||||
$document = $ctx->getMessage()?->getDocument();
|
||||
|
||||
if ($document instanceof telegram_document) {
|
||||
// Инициализирован документ
|
||||
|
||||
// Инициализация файла
|
||||
$ctx->getFile($document->getFileId())->then(function ($file) use ($ctx, $document, $account) {
|
||||
|
||||
if ($file->getFileSize() <= 50000000) {
|
||||
// Не превышает 50 мегабайт (50 000 000 байт) размер файла
|
||||
|
||||
if (pathinfo(parse_url($file->getFilePath())['path'], PATHINFO_EXTENSION) === 'xlsx') {
|
||||
// Имеет расширение xlsx файл
|
||||
|
||||
// Initializing the directory in the storage
|
||||
if (!file_exists($storage = STORAGE . DIRECTORY_SEPARATOR . 'import' . DIRECTORY_SEPARATOR . $account->getKey() . DIRECTORY_SEPARATOR . time()))
|
||||
mkdir($storage, 0775, true);
|
||||
|
||||
// Сохранение файла
|
||||
file_put_contents(
|
||||
$import = $storage . DIRECTORY_SEPARATOR . 'import.xlsx',
|
||||
file_get_contents('https://api.telegram.org/file/bot' . KEY . '/' . parse_url($file->getFilePath())['path'])
|
||||
);
|
||||
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage(sprintf(
|
||||
<<<'TXT'
|
||||
🔬 *Выполняется анализ:* %s \(%s байт\)
|
||||
TXT,
|
||||
static::unmarkdown($document->getFileName()),
|
||||
static::unmarkdown((string) $file->getFileSize())
|
||||
))
|
||||
->then(function ($message) use ($ctx, $import) {
|
||||
// Инициализация счётчика загруженных товаров
|
||||
$categories_loaded
|
||||
= $products_loaded
|
||||
= $categories_created
|
||||
= $products_created
|
||||
= $categories_updated
|
||||
= $products_updated
|
||||
= $categories_deleted
|
||||
= $products_deleted
|
||||
= $categories_old
|
||||
= $products_old
|
||||
= $categories_new
|
||||
= $products_new
|
||||
= 0;
|
||||
|
||||
// Import
|
||||
catalog::import(
|
||||
$import,
|
||||
$categories_loaded,
|
||||
$categories_created,
|
||||
$categories_updated,
|
||||
$categories_deleted,
|
||||
$categories_old,
|
||||
$categories_new,
|
||||
$products_loaded,
|
||||
$products_created,
|
||||
$products_updated,
|
||||
$products_deleted,
|
||||
$products_old,
|
||||
$products_new,
|
||||
language: $account->language ?? settings::active()?->language ?? 'en'
|
||||
);
|
||||
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage(<<<TXT
|
||||
🏷 *Категории*
|
||||
|
||||
*Загружено:* $categories_loaded
|
||||
|
||||
*Добавлено:* $categories_created
|
||||
*Обновлено:* $categories_updated
|
||||
*Удалено:* $categories_deleted
|
||||
|
||||
*Было:* $categories_old
|
||||
*Стало:* $categories_new
|
||||
TXT)
|
||||
->then(function ($message) use ($ctx, $products_loaded, $products_created, $products_updated, $products_deleted, $products_old, $products_new) {
|
||||
$ctx->sendMessage(<<<TXT
|
||||
📦 *Товары*
|
||||
|
||||
*Загружено:* $products_loaded
|
||||
|
||||
*Добавлено:* $products_created
|
||||
*Обновлено:* $products_updated
|
||||
*Удалено:* $products_deleted
|
||||
|
||||
*Было:* $products_old
|
||||
*Стало:* $products_new
|
||||
TXT)
|
||||
->then(function ($message) use ($ctx) {
|
||||
// Завершение диалога
|
||||
$ctx->endConversation();
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Не имеет расширение xlsx файл
|
||||
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage(static::unmarkdown('Файл должен иметь расширение xlsx'));
|
||||
}
|
||||
} else {
|
||||
// Превышает 50 мегабайт (50000000 байт) размер файла
|
||||
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage(static::unmarkdown('Размер файла не должен превышать 50 мегабайт'));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Не инициализирован документ
|
||||
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage(static::unmarkdown('Отправьте документ в формате xlsx со списком товаров'));
|
||||
}
|
||||
} else {
|
||||
// Не авторизован доступ к настройкам
|
||||
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage('⛔ *Нет доступа*');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Инициализация аккаунта (middleware)
|
||||
*
|
||||
* @param Context $ctx
|
||||
* @param Node $next
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function account(Context $ctx, Node $next): void
|
||||
{
|
||||
// Выполнение заблокировано?
|
||||
if ($ctx->get('stop')) return;
|
||||
|
||||
// Инициализация аккаунта Telegram
|
||||
$telegram = $ctx->getEffectiveUser();
|
||||
|
||||
// Инициализация аккаунта
|
||||
$account = account::initialize($telegram->getId(), $telegram);
|
||||
|
||||
if ($account) {
|
||||
// Инициализирован аккаунт
|
||||
|
||||
if ($account->banned) {
|
||||
// Заблокирован аккаунт
|
||||
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage('⛔ *Ты заблокирован*')
|
||||
->then(function ($message) use ($ctx) {
|
||||
// Завершение диалога
|
||||
$ctx->endConversation();
|
||||
});
|
||||
|
||||
// Блокировка дальнейшего выполнения
|
||||
$ctx->set('stop', true);
|
||||
} else {
|
||||
// Не заблокирован аккаунт
|
||||
|
||||
// Запись в буфер
|
||||
$ctx->set('account', $account);
|
||||
|
||||
// Продолжение выполнения
|
||||
$next($ctx);
|
||||
}
|
||||
} else {
|
||||
// Не инициализирован аккаунт
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Инициализация статуса технических работ (middleware)
|
||||
*
|
||||
* @param Context $ctx
|
||||
* @param Node $next
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function suspension(Context $ctx, Node $next): void
|
||||
{
|
||||
// Выполнение заблокировано?
|
||||
if ($ctx->get('stop')) return;
|
||||
|
||||
// Поиск технических работ
|
||||
$suspension = suspension::search();
|
||||
|
||||
if ($suspension && $suspension->targets['telegram-robot']) {
|
||||
// Найдена активная приостановка
|
||||
|
||||
// Инициализация аккаунта
|
||||
$account = $ctx->get('account');
|
||||
|
||||
if ($account) {
|
||||
// Инициализирован аккаунт
|
||||
|
||||
foreach ($suspension->access as $type => $status) {
|
||||
// Перебор статусов доступа
|
||||
|
||||
if ($status && $account->{$type}) {
|
||||
// Авторизован аккаунт
|
||||
|
||||
// Продолжение выполнения
|
||||
$next($ctx);
|
||||
|
||||
// Выход (успех)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация сообщения
|
||||
$message = "⚠️ *Работа приостановлена*\n*Оставшееся время\:* " . $suspension->message($account->language ?? settings::active()?->language ?? 'en');
|
||||
|
||||
// Добавление описания причины приостановки, если найдена
|
||||
if (!empty($suspension->description))
|
||||
$message .= "\n\n" . $suspension->description[$account->language ?? settings::active()?->language ?? 'en'] ?? array_values($suspension->description)[0];
|
||||
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage($message)
|
||||
->then(function ($message) use ($ctx) {
|
||||
// Завершение диалога
|
||||
$ctx->endConversation();
|
||||
});
|
||||
|
||||
// Блокировка дальнейшего выполнения
|
||||
$ctx->set('stop', true);
|
||||
} else {
|
||||
// Не найдена активная приостановка
|
||||
|
||||
// Продолжение выполнения
|
||||
$next($ctx);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\traits;
|
||||
namespace mirzaev\arming_bot\models\traits;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\models\interfaces\collection as collection_interface,
|
||||
mirzaev\huesos\models\enumerations\language,
|
||||
mirzaev\huesos\models\enumerations\currency;
|
||||
use mirzaev\arming_bot\models\interfaces\collection as collection_interface,
|
||||
mirzaev\arming_bot\models\enumerations\language,
|
||||
mirzaev\arming_bot\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\huesos\models\traits
|
||||
* @package mirzaev\arming_bot\models\traits
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
|
@ -50,21 +50,21 @@ trait buffer
|
|||
// Initialized the collection
|
||||
|
||||
// Is the instance of the document from ArangoDB are initialized?
|
||||
if (!($this->__document() instanceof _document)) throw new exception('The instance of the document from ArangoDB is not initialized');
|
||||
if (!isset($this->document)) throw new exception('The instance of the sessoin document from ArangoDB is not initialized');
|
||||
|
||||
// Writing data into buffer of the instance of the document from ArangoDB
|
||||
$this->__document()->buffer = array_replace_recursive($this->document->buffer ?? [], $data);
|
||||
$this->document->buffer = array_replace_recursive($this->document->buffer ?? [], $data);
|
||||
|
||||
// Is the buffer of the instance of the document from ArangoDB exceed 10 megabytes?
|
||||
if (mb_strlen(json_encode($this->__document()->buffer)) > 10485760) throw new exception('The buffer size exceeds 10 megabytes');
|
||||
if (mb_strlen(json_encode($this->document->buffer)) > 10485760) throw new exception('The buffer size exceeds 10 megabytes');
|
||||
|
||||
// Serializing parameters @todo ЗАЧЕМУ ЭТО ЗДЕСЬ?
|
||||
/* if ($this->__document()->language instanceof language) $this->__document()->language = $this->__document()->language->name;
|
||||
if ($this->__document()->currency instanceof currency) $this->__document()->currency = $this->__document()->currency->name; */
|
||||
// Serializing parameters
|
||||
if ($this->document->language instanceof language) $this->document->language = $this->document->language->name;
|
||||
if ($this->document->currency instanceof currency) $this->document->currency = $this->document->currency->name;
|
||||
|
||||
// Writing to ArangoDB and exit (success)
|
||||
return document::update($this->__document(), errors: $errors);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
return document::update($this->document, errors: $errors);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\traits;
|
||||
namespace mirzaev\arming_bot\models\traits;
|
||||
|
||||
// Files of the project
|
||||
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;
|
||||
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;
|
||||
|
||||
// Library для ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
@ -28,7 +28,7 @@ use exception;
|
|||
*
|
||||
* @uses collection_interface
|
||||
* @uses document_interface
|
||||
* @package mirzaev\huesos\models\traits
|
||||
* @package mirzaev\arming_bot\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->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);
|
||||
} 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);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\traits;
|
||||
namespace mirzaev\arming_bot\models\traits;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\models\interfaces\document as document_interface,
|
||||
mirzaev\huesos\models\interfaces\collection as collection_interface,
|
||||
mirzaev\huesos\models\connect;
|
||||
use mirzaev\arming_bot\models\interfaces\document as document_interface,
|
||||
mirzaev\arming_bot\models\interfaces\collection as collection_interface,
|
||||
mirzaev\arming_bot\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\huesos\models\traits
|
||||
* @package mirzaev\arming_bot\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,12 +79,10 @@ 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 "connect" edge collection, if created or found
|
||||
* @return string|null The identifier of the created edge of the "connect" collection, if created
|
||||
*/
|
||||
public function connect(collection_interface $document, array &$errors = []): ?string
|
||||
{
|
||||
|
@ -97,50 +95,19 @@ trait document
|
|||
if ($this->document instanceof _document) {
|
||||
// Initialized instance of the document from ArangoDB
|
||||
|
||||
// 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,
|
||||
// Writing document and exit (success)
|
||||
return framework_document::write(
|
||||
connect::COLLECTION,
|
||||
[
|
||||
'@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->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);
|
||||
} 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);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\traits;
|
||||
namespace mirzaev\arming_bot\models\traits;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
@ -10,7 +10,7 @@ use exception;
|
|||
/**
|
||||
* Trait for initialization of files handlers
|
||||
*
|
||||
* @package mirzaev\huesos\models\traits
|
||||
* @package mirzaev\arming_bot\models\traits
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\traits;
|
||||
namespace mirzaev\arming_bot\models\traits;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
@ -10,7 +10,7 @@ use exception;
|
|||
/**
|
||||
* Trait for initialization of a status
|
||||
*
|
||||
* @package mirzaev\huesos\models\traits
|
||||
* @package mirzaev\arming_bot\models\traits
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\arming_bot\models\traits\yandex;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Trait for "Yandex Disk"
|
||||
*
|
||||
* @package mirzaev\arming_bot\models\traits\yandex
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
trait disk
|
||||
{
|
||||
/**
|
||||
* Download file from "Yandex Disk"
|
||||
*
|
||||
* @param string $uri URI of the file from "Yandex Disk"
|
||||
* @param string $destination Destination to write the file
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return bool The file is downloaded?
|
||||
*/
|
||||
private static function download(
|
||||
string $uri,
|
||||
string $destination,
|
||||
array &$errors = []
|
||||
): bool {
|
||||
try {
|
||||
if (!empty($uri)) {
|
||||
// Not empty URI
|
||||
|
||||
if (!empty($destination)) {
|
||||
// Not empty destination
|
||||
|
||||
// Initializing URL of the file
|
||||
$url = "https://cloud-api.yandex.net/v1/disk/public/resources/download?public_key=$uri";
|
||||
|
||||
// Checking if the file is available for download
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($code === 200) {
|
||||
// The file is available for download
|
||||
|
||||
// Downloading the file and exit (success)
|
||||
return file_put_contents($destination, file_get_contents(json_decode(file_get_contents($url))?->href)) > 0;
|
||||
} else throw new exception("File not available for download: $uri");
|
||||
} else throw new exception("Empty destination");
|
||||
} else throw new exception("Empty URI");
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
<?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;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\core,
|
||||
mirzaev\minimal\router;
|
||||
|
||||
ini_set('error_reporting', E_ALL);
|
||||
ini_set('display_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('INDEX', __DIR__);
|
||||
define('THEME', 'default');
|
||||
|
||||
// Initialize dependencies
|
||||
require __DIR__ . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. 'vendor' . DIRECTORY_SEPARATOR
|
||||
. 'autoload.php';
|
||||
|
||||
// Initialize the router
|
||||
$router = new router;
|
||||
|
||||
// Initialize routes
|
||||
$router
|
||||
->write('/', 'catalog', 'index', 'GET')
|
||||
->write('/', 'catalog', 'index', 'POST')
|
||||
->write('/cart', 'cart', 'index', 'GET')
|
||||
->write('/cart', 'cart', 'index', 'POST')
|
||||
->write('/cart/product', 'cart', 'product', 'POST')
|
||||
->write('/cart/summary', 'cart', 'summary', 'POST')
|
||||
->write('/account/write', 'account', 'write', 'POST')
|
||||
->write('/session/write', 'session', 'write', 'POST')
|
||||
->write('/session/connect/telegram', 'session', 'telegram', 'POST')
|
||||
->write('/cdek/calculate', 'deliveries\\cdek', 'calculate', 'POST')
|
||||
/* ->write('/category/$identifier', 'catalog', 'index', 'POST') */
|
||||
/* ->write('/category', 'catalog', 'index', 'POST') */
|
||||
/* ->write('/product/$identifier', 'catalog', 'product', 'POST') */
|
||||
;
|
||||
|
||||
/*
|
||||
|
||||
// Initializing of routes
|
||||
$router
|
||||
->write('/', 'catalog', 'index', 'GET')
|
||||
->write('/$sex', 'catalog', 'search', 'POST')
|
||||
->write('/$search', 'catalog', 'search', 'POST')
|
||||
->write('/search', 'catalog', 'search', 'POST')
|
||||
->write('/search/$asdasdasd', 'catalog', 'search', 'POST')
|
||||
->write('/ebala/$sex/$categories...', 'catalog', 'index', 'POST')
|
||||
->write('/$sex/$categories...', 'catalog', 'index', 'POST')
|
||||
->write('/$categories...', 'catalog', 'index', 'POST')
|
||||
->write('/ebala/$categories...', 'catalog', 'index', 'POST');
|
||||
|
||||
var_dump($router->routes);
|
||||
echo "\n\n\n\n\n\n";
|
||||
$router
|
||||
->sort();
|
||||
var_dump($router->routes); */
|
||||
|
||||
// Initialize the core
|
||||
$core = new core(namespace: __NAMESPACE__, router: $router, controller: new controller(false), model: new model(false));
|
||||
|
||||
// Handle the request
|
||||
echo $core->start();
|
|
@ -0,0 +1,20 @@
|
|||
"use strict";
|
||||
|
||||
core.modules.connect(["session", "account", "telegram"])
|
||||
.then(() => {
|
||||
//
|
||||
const { initData, initDataUnsafe, ...data } = core.telegram.api;
|
||||
|
||||
//
|
||||
core.session.buffer.write("telegram_program", JSON.stringify(data));
|
||||
|
||||
if (core.telegram.api.initData.length > 0) {
|
||||
//
|
||||
|
||||
//
|
||||
core.account.authentication();
|
||||
}
|
||||
|
||||
//
|
||||
core.telegram.api.ready();
|
||||
});
|
|
@ -17,11 +17,11 @@ class core {
|
|||
// Window
|
||||
static window;
|
||||
|
||||
// Account
|
||||
static account;
|
||||
|
||||
// The "loading" element
|
||||
static loading = document.getElementById("loading");
|
||||
static status_loading = document.getElementById("loading");
|
||||
|
||||
// The "account" element
|
||||
static status_account = document.getElementById("account");
|
||||
|
||||
// The <header> element
|
||||
static header = document.body.getElementsByTagName("header")[0];
|
||||
|
@ -52,7 +52,7 @@ class core {
|
|||
static async request(
|
||||
uri = "/",
|
||||
body,
|
||||
method = "GET",
|
||||
method = "POST",
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Accept": "application/json",
|
||||
|
@ -320,7 +320,7 @@ Object.assign(
|
|||
// Downloading, importing and writing the module into a core property and into registry of loaded modules
|
||||
core[module] =
|
||||
loaded[module] =
|
||||
await (await import(`./modules/${module}.mjs`)).default;
|
||||
await (await import(`./modules/${module}.js`)).default;
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
|
@ -351,7 +351,7 @@ core.modules.connect("damper").then(() => {
|
|||
damper: core.damper(
|
||||
(...variables) => core.buffer.write.system(...variables),
|
||||
300,
|
||||
2,
|
||||
3,
|
||||
),
|
||||
},
|
||||
);
|
||||
|
@ -382,14 +382,14 @@ Object.assign(
|
|||
// Imported the session module
|
||||
|
||||
// Write to the session buffer
|
||||
core.session.buffer?.write(name, value);
|
||||
session.default.buffer?.write(name, value);
|
||||
});
|
||||
|
||||
core.modules.connect("account").then(() => {
|
||||
// Imported the account module
|
||||
|
||||
// Write to the account buffer
|
||||
core.account.buffer?.write(name, value);
|
||||
account.default.buffer?.write(name, value);
|
||||
});
|
||||
|
||||
// Exit (success)
|
|
@ -0,0 +1,750 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* @name Бегущая строка
|
||||
*
|
||||
* @description
|
||||
* Простой, но мощный класс для создания бегущих строк. Поддерживает
|
||||
* перемещение мышью и прокрутку колесом, полностью настраивается очень гибок
|
||||
* для настроек в CSS и подразумевается, что отлично индексируется поисковыми роботами.
|
||||
* Имеет свой препроцессор, благодаря которому можно создавать бегущие строки
|
||||
* без программирования - с помощью HTML-аттрибутов, а так же возможность
|
||||
* изменять параметры (data-hotline-* аттрибуты) на лету. Есть возможность вызывать
|
||||
* события при выбранных действиях для того, чтобы пользователь имел возможность
|
||||
* дорабатывать функционал без изучения и изменения моего кода
|
||||
*
|
||||
* @example
|
||||
* сonst hotline = new hotline();
|
||||
* hotline.step = '-5';
|
||||
* hotline.start();
|
||||
*
|
||||
* @todo
|
||||
* 1. Бесконечный режим - элементы не удаляются если видны на экране (будут дубликаты).
|
||||
* Сейчас при БЫСТРОМ прокручивании можно заметит как элементы "появляются" в начале и конце строки.
|
||||
* 2. "gap" and "padding" in wrap should be removed! or added here to the calculations
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
export default class hotline {
|
||||
// Идентификатор
|
||||
#id = 0;
|
||||
|
||||
// Оболочка (instanceof HTMLElement)
|
||||
#shell = document.getElementById("hotline");
|
||||
|
||||
// Инстанция горячей строки
|
||||
#instance = null;
|
||||
|
||||
// Перемещение
|
||||
#transfer = true;
|
||||
|
||||
// Движение
|
||||
#move = true;
|
||||
|
||||
// Наблюдатель
|
||||
#observer = null;
|
||||
|
||||
// Реестр запрещённых к изменению параметров
|
||||
#block = new Set(["events"]);
|
||||
|
||||
// Status (null, active, inactive)
|
||||
#status = null;
|
||||
|
||||
// Настраиваемые параметры
|
||||
transfer = null;
|
||||
move = null;
|
||||
delay = 10;
|
||||
step = 1;
|
||||
hover = true;
|
||||
movable = true;
|
||||
sticky = false;
|
||||
wheel = false;
|
||||
delta = null;
|
||||
vertical = false;
|
||||
button = 0; // button for grabbing. 0 is main mouse button (left)
|
||||
observe = false;
|
||||
events = new Map([
|
||||
["start", false],
|
||||
["stop", false],
|
||||
["move", false],
|
||||
["move.block", false],
|
||||
["move.unblock", false],
|
||||
["offset", false],
|
||||
["transfer.start", true],
|
||||
["transfer.end", true],
|
||||
["mousemove", false],
|
||||
["touchmove", false],
|
||||
]);
|
||||
|
||||
// Is hotline currently moving due to "onmousemove" or "ontouchmove"?
|
||||
moving = false;
|
||||
|
||||
constructor(id, shell) {
|
||||
// Запись идентификатора
|
||||
if (typeof id === "string" || typeof id === "number") this.#id = id;
|
||||
|
||||
// Запись оболочки
|
||||
if (shell instanceof HTMLElement) this.#shell = shell;
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this.#instance === null) {
|
||||
// Нет запущенной инстанции бегущей строки
|
||||
|
||||
// Инициализация ссылки на ядро
|
||||
const _this = this;
|
||||
|
||||
// Запуск движения
|
||||
this.#instance = setInterval(function () {
|
||||
if (_this.#shell.childElementCount > 1) {
|
||||
// Найдено содержимое бегущей строки (2 и более)
|
||||
|
||||
// Инициализация буфера для временных данных
|
||||
let buffer;
|
||||
|
||||
// Инициализация данных первого элемента в строке
|
||||
const first = {
|
||||
element: (buffer = _this.#shell.firstElementChild),
|
||||
coords: buffer.getBoundingClientRect(),
|
||||
};
|
||||
|
||||
if (_this.vertical) {
|
||||
// Вертикальная бегущая строка
|
||||
|
||||
// Инициализация сдвига у первого элемента (движение)
|
||||
first.offset = isNaN(
|
||||
buffer = parseFloat(first.element.style.marginTop),
|
||||
)
|
||||
? 0
|
||||
: buffer;
|
||||
|
||||
// Инициализация отступа до второго элемента у первого элемента (разделение)
|
||||
first.separator = isNaN(
|
||||
buffer = parseFloat(
|
||||
getComputedStyle(first.element).marginBottom,
|
||||
),
|
||||
)
|
||||
? 0
|
||||
: buffer;
|
||||
|
||||
// Инициализация крайнего с конца ребра первого элемента в строке
|
||||
first.end = first.coords.y + first.coords.height +
|
||||
first.separator;
|
||||
} else {
|
||||
// Горизонтальная бегущая строка
|
||||
|
||||
// Инициализация отступа у первого элемента (движение)
|
||||
first.offset = isNaN(
|
||||
buffer = parseFloat(first.element.style.marginLeft),
|
||||
)
|
||||
? 0
|
||||
: buffer;
|
||||
|
||||
// Инициализация отступа до второго элемента у первого элемента (разделение)
|
||||
first.separator = isNaN(
|
||||
buffer = parseFloat(
|
||||
getComputedStyle(first.element).marginRight,
|
||||
),
|
||||
)
|
||||
? 0
|
||||
: buffer;
|
||||
|
||||
// Инициализация крайнего с конца ребра первого элемента в строке
|
||||
first.end = first.coords.x + first.coords.width +
|
||||
first.separator;
|
||||
}
|
||||
|
||||
if (
|
||||
(_this.vertical &&
|
||||
Math.round(first.end) < _this.#shell.offsetTop) ||
|
||||
(!_this.vertical &&
|
||||
Math.round(first.end) < _this.#shell.offsetLeft)
|
||||
) {
|
||||
// Элемент (вместе с отступом до второго элемента) вышел из области видимости (строки)
|
||||
|
||||
if (
|
||||
(_this.transfer === null && _this.#transfer) ||
|
||||
_this.transfer === true
|
||||
) {
|
||||
// Перенос разрешен
|
||||
|
||||
if (_this.vertical) {
|
||||
// Вертикальная бегущая строка
|
||||
|
||||
// Удаление отступов (движения)
|
||||
first.element.style.marginTop = null;
|
||||
} else {
|
||||
// Горизонтальная бегущая строка
|
||||
|
||||
// Удаление отступов (движения)
|
||||
first.element.style.marginLeft = null;
|
||||
}
|
||||
|
||||
// Копирование первого элемента в конец строки
|
||||
_this.#shell.appendChild(first.element);
|
||||
|
||||
if (_this.events.get("transfer.end")) {
|
||||
// Запрошен вызов события: "перемещение в конец"
|
||||
|
||||
// Вызов события: "перемещение в конец"
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(`hotline.${_this.#id}.transfer.end`, {
|
||||
detail: {
|
||||
element: first.element,
|
||||
offset: -(
|
||||
(_this.vertical
|
||||
? first.coords.height
|
||||
: first.coords.width) + first.separator
|
||||
),
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
(_this.vertical &&
|
||||
Math.round(first.coords.y) > _this.#shell.offsetTop) ||
|
||||
(!_this.vertical &&
|
||||
Math.round(first.coords.x) > _this.#shell.offsetLeft)
|
||||
) {
|
||||
// Передняя (движущая) граница первого элемента вышла из области видимости
|
||||
|
||||
if (
|
||||
(_this.transfer === null && _this.#transfer) ||
|
||||
_this.transfer === true
|
||||
) {
|
||||
// Перенос разрешен
|
||||
|
||||
// Инициализация отступа у последнего элемента (разделение)
|
||||
const separator = (buffer = isNaN(
|
||||
buffer = parseFloat(
|
||||
getComputedStyle(_this.#shell.lastElementChild)[
|
||||
_this.vertical ? "marginBottom" : "marginRight"
|
||||
],
|
||||
),
|
||||
)
|
||||
? 0
|
||||
: buffer) === 0
|
||||
? first.separator
|
||||
: buffer;
|
||||
|
||||
// Инициализация координат первого элемента в строке
|
||||
const coords = _this.#shell.lastElementChild
|
||||
.getBoundingClientRect();
|
||||
|
||||
if (_this.vertical) {
|
||||
// Вертикальная бегущая строка
|
||||
|
||||
// Удаление отступов (движения)
|
||||
_this.#shell.lastElementChild.style.marginTop = -coords.height -
|
||||
separator + "px";
|
||||
} else {
|
||||
// Горизонтальная бегущая строка
|
||||
|
||||
// Удаление отступов (движения)
|
||||
_this.#shell.lastElementChild.style.marginLeft = -coords.width -
|
||||
separator + "px";
|
||||
}
|
||||
|
||||
// Копирование последнего элемента в начало строки
|
||||
_this.#shell.insertBefore(
|
||||
_this.#shell.lastElementChild,
|
||||
first.element,
|
||||
);
|
||||
|
||||
// Удаление отступов у второго элемента в строке (движения)
|
||||
_this.#shell.children[1].style[
|
||||
_this.vertical ? "marginTop" : "marginLeft"
|
||||
] = null;
|
||||
|
||||
if (_this.events.get("transfer.start")) {
|
||||
// Запрошен вызов события: "перемещение в начало"
|
||||
|
||||
// Вызов события: "перемещение в начало"
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(`hotline.${_this.#id}.transfer.start`, {
|
||||
detail: {
|
||||
element: _this.#shell.lastElementChild,
|
||||
offset: (_this.vertical ? coords.height : coords.width) +
|
||||
separator,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Элемент в области видимости
|
||||
|
||||
if (
|
||||
(_this.move === null && _this.#move) || _this.move === true
|
||||
) {
|
||||
// Движение разрешено
|
||||
|
||||
// Запись новых координат сдвига
|
||||
const offset = first.offset + _this.step;
|
||||
|
||||
// Запись сдвига (движение)
|
||||
_this.offset(offset);
|
||||
|
||||
if (_this.events.get("move")) {
|
||||
// Запрошен вызов события: "движение"
|
||||
|
||||
// Вызов события: "движение"
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(`hotline.${_this.#id}.move`, {
|
||||
detail: {
|
||||
from: first.offset,
|
||||
to: offset,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, _this.delay);
|
||||
|
||||
if (this.hover) {
|
||||
// Запрошена возможность останавливать бегущую строку
|
||||
|
||||
// Инициализация сдвига
|
||||
let offset = 0;
|
||||
|
||||
// Инициализация слушателя события при перемещении элемента в бегущей строке
|
||||
const listener = function (e) {
|
||||
// Увеличение сдвига
|
||||
offset += e.detail.offset ?? 0;
|
||||
};
|
||||
|
||||
// Объявление переменной в области видимости обработки остановки бегущей строки
|
||||
let move;
|
||||
|
||||
// Инициализация обработчика наведения курсора (остановка движения)
|
||||
this.#shell.onmouseover = function (e) {
|
||||
// Курсор наведён на бегущую строку
|
||||
|
||||
// Блокировка движения
|
||||
_this.#move = false;
|
||||
|
||||
if (_this.events.get("move.block")) {
|
||||
// Запрошен вызов события: "блокировка движения"
|
||||
|
||||
// Вызов события: "блокировка движения"
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(`hotline.${_this.#id}.move.block`),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.movable) {
|
||||
// Запрошена возможность двигать бегущую строку
|
||||
|
||||
_this.#shell.onmousedown =
|
||||
_this.#shell.ontouchstart =
|
||||
function (
|
||||
start,
|
||||
) {
|
||||
// Handling a "mousedown" and a "touchstart" on hotline
|
||||
|
||||
if (
|
||||
start.type === "touchstart" ||
|
||||
start.button === _this.button
|
||||
) {
|
||||
const x = start.pageX || start.touches[0].pageX;
|
||||
const y = start.pageY || start.touches[0].pageY;
|
||||
|
||||
// Блокировка движения
|
||||
_this.#move = false;
|
||||
|
||||
if (_this.events.get("move.block")) {
|
||||
// Запрошен вызов события: "блокировка движения"
|
||||
|
||||
// Вызов события: "блокировка движения"
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(`hotline.${_this.#id}.move.block`),
|
||||
);
|
||||
}
|
||||
|
||||
// Инициализация слушателей события перемещения элемента в бегущей строке
|
||||
document.addEventListener(
|
||||
`hotline.${_this.#id}.transfer.start`,
|
||||
listener,
|
||||
);
|
||||
document.addEventListener(
|
||||
`hotline.${_this.#id}.transfer.end`,
|
||||
listener,
|
||||
);
|
||||
|
||||
// Инициализация буфера для временных данных
|
||||
let buffer;
|
||||
|
||||
// Инициализация данных первого элемента в строке
|
||||
const first = {
|
||||
offset: isNaN(
|
||||
buffer = parseFloat(
|
||||
_this.vertical
|
||||
? _this.#shell.firstElementChild.style
|
||||
.marginTop
|
||||
: _this.#shell.firstElementChild.style
|
||||
.marginLeft,
|
||||
),
|
||||
)
|
||||
? 0
|
||||
: buffer,
|
||||
};
|
||||
|
||||
move = (move) => {
|
||||
// Обработка движения курсора
|
||||
|
||||
if (_this.#status === "active") {
|
||||
// Запись статуса ручного перемещения
|
||||
_this.moving = true;
|
||||
|
||||
const _x = move.pageX || move.touches[0].pageX;
|
||||
const _y = move.pageY || move.touches[0].pageY;
|
||||
|
||||
if (_this.vertical) {
|
||||
// Вертикальная бегущая строка
|
||||
|
||||
// Инициализация буфера местоположения
|
||||
const from =
|
||||
_this.#shell.firstElementChild.style.marginTop;
|
||||
const to = _y - (y + offset - first.offset);
|
||||
|
||||
// Движение
|
||||
_this.#shell.firstElementChild.style.marginTop = to +
|
||||
"px";
|
||||
} else {
|
||||
// Горизонтальная бегущая строка
|
||||
|
||||
// Инициализация буфера местоположения
|
||||
const from =
|
||||
_this.#shell.firstElementChild.style.marginLeft;
|
||||
const to = _x - (x + offset - first.offset);
|
||||
|
||||
// Движение
|
||||
_this.#shell.firstElementChild.style.marginLeft = to +
|
||||
"px";
|
||||
}
|
||||
|
||||
if (_this.events.get(move.type)) {
|
||||
// Запрошен вызов события: "перемещение" (мышью или касанием)
|
||||
|
||||
// Вызов события: "перемещение" (мышью или касанием)
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(
|
||||
`hotline.${_this.#id}.${move.type}`,
|
||||
{
|
||||
detail: { from, to },
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Запись курсора
|
||||
_this.#shell.style.cursor = "grabbing";
|
||||
}
|
||||
};
|
||||
|
||||
// Запуск обработки движения
|
||||
document.addEventListener("mousemove", move);
|
||||
document.addEventListener("touchmove", move);
|
||||
}
|
||||
};
|
||||
|
||||
// Перещапись событий браузера (чтобы не дёргалось)
|
||||
_this.#shell.ondragstart = null;
|
||||
|
||||
_this.#shell.onmouseup = _this.#shell.ontouchend = function () {
|
||||
// Курсор деактивирован
|
||||
|
||||
// Запись статуса ручного перемещения
|
||||
_this.moving = false;
|
||||
|
||||
// Остановка обработки движения
|
||||
document.removeEventListener("mousemove", move);
|
||||
document.removeEventListener("touchmove", move);
|
||||
|
||||
// Сброс сдвига
|
||||
offset = 0;
|
||||
|
||||
document.removeEventListener(
|
||||
`hotline.${_this.#id}.transfer.start`,
|
||||
listener,
|
||||
);
|
||||
document.removeEventListener(
|
||||
`hotline.${_this.#id}.transfer.end`,
|
||||
listener,
|
||||
);
|
||||
|
||||
// Разблокировка движения
|
||||
_this.#move = true;
|
||||
|
||||
if (_this.events.get("move.unblock")) {
|
||||
// Запрошен вызов события: "разблокировка движения"
|
||||
|
||||
// Вызов события: "разблокировка движения"
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(`hotline.${_this.#id}.move.unblock`),
|
||||
);
|
||||
}
|
||||
|
||||
// Восстановление курсора
|
||||
_this.#shell.style.cursor = null;
|
||||
};
|
||||
}
|
||||
|
||||
// Инициализация обработчика отведения курсора (остановка движения)
|
||||
this.#shell.onmouseleave = function (onmouseleave) {
|
||||
// Курсор отведён от бегущей строки
|
||||
|
||||
if (!_this.sticky) {
|
||||
// Отключено прилипание
|
||||
|
||||
// Запись статуса ручного перемещения
|
||||
_this.moving = false;
|
||||
|
||||
// Остановка обработки движения
|
||||
document.removeEventListener("mousemove", move);
|
||||
document.removeEventListener("touchmove", move);
|
||||
|
||||
document.removeEventListener(
|
||||
`hotline.${_this.#id}.transfer.start`,
|
||||
listener,
|
||||
);
|
||||
document.removeEventListener(
|
||||
`hotline.${_this.#id}.transfer.end`,
|
||||
listener,
|
||||
);
|
||||
|
||||
// Восстановление курсора
|
||||
_this.#shell.style.cursor = null;
|
||||
}
|
||||
|
||||
// Сброс сдвига
|
||||
offset = 0;
|
||||
|
||||
// Разблокировка движения
|
||||
_this.#move = true;
|
||||
|
||||
if (_this.events.get("move.unblock")) {
|
||||
// Запрошен вызов события: "разблокировка движения"
|
||||
|
||||
// Вызов события: "разблокировка движения"
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(`hotline.${_this.#id}.move.unblock`),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (this.wheel) {
|
||||
// Запрошена возможность прокручивать колесом мыши
|
||||
|
||||
// Инициализация обработчика наведения курсора (остановка движения)
|
||||
this.#shell.onwheel = function (e) {
|
||||
// Курсор наведён на бегущую
|
||||
|
||||
// Инициализация буфера для временных данных
|
||||
let buffer;
|
||||
|
||||
// Перемещение
|
||||
_this.offset(
|
||||
(isNaN(
|
||||
buffer = parseFloat(
|
||||
_this.#shell.firstElementChild.style[
|
||||
_this.vertical ? "marginTop" : "marginLeft"
|
||||
],
|
||||
),
|
||||
)
|
||||
? 0
|
||||
: buffer) +
|
||||
(_this.delta === null
|
||||
? e.wheelDelta
|
||||
: e.wheelDelta > 0
|
||||
? _this.delta
|
||||
: -_this.delta),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
this.#status = "active";
|
||||
}
|
||||
|
||||
if (this.observe) {
|
||||
// Запрошено наблюдение за изменениями аттрибутов элемента бегущей строки
|
||||
|
||||
if (this.#observer === null) {
|
||||
// Отсутствует наблюдатель
|
||||
|
||||
// Инициализация ссылки на ядро
|
||||
const _this = this;
|
||||
|
||||
// Инициализация наблюдателя
|
||||
this.#observer = new MutationObserver(function (mutations) {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type === "attributes") {
|
||||
// Запись параметра в инстанцию бегущей строки
|
||||
_this.configure(mutation.attributeName);
|
||||
}
|
||||
}
|
||||
|
||||
// Перезапуск бегущей строки
|
||||
_this.restart();
|
||||
});
|
||||
|
||||
// Активация наблюдения
|
||||
this.#observer.observe(this.#shell, {
|
||||
attributes: true,
|
||||
});
|
||||
}
|
||||
} else if (this.#observer instanceof MutationObserver) {
|
||||
// Запрошено отключение наблюдения
|
||||
|
||||
// Деактивация наблюдения
|
||||
this.#observer.disconnect();
|
||||
|
||||
// Удаление наблюдателя
|
||||
this.#observer = null;
|
||||
}
|
||||
|
||||
if (this.events.get("start")) {
|
||||
// Запрошен вызов события: "запуск"
|
||||
|
||||
// Вызов события: "запуск"
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(`hotline.${this.#id}.start`),
|
||||
);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.#status = "inactive";
|
||||
|
||||
// Остановка бегущей строки
|
||||
clearInterval(this.#instance);
|
||||
|
||||
// Удаление инстанции интервала
|
||||
this.#instance = null;
|
||||
|
||||
if (this.events.get("stop")) {
|
||||
// Запрошен вызов события: "остановка"
|
||||
|
||||
// Вызов события: "остановка"
|
||||
document.dispatchEvent(new CustomEvent(`hotline.${this.#id}.stop`));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
restart() {
|
||||
// Остановка бегущей строки
|
||||
this.stop();
|
||||
|
||||
// Запуск бегущей строки
|
||||
this.start();
|
||||
}
|
||||
|
||||
configure(attribute) {
|
||||
// Инициализация названия параметра
|
||||
const parameter = (/^data-hotline-(\w+)$/.exec(attribute) ?? [, null])[1];
|
||||
|
||||
if (typeof parameter === "string") {
|
||||
// Параметр найден
|
||||
|
||||
// Проверка на разрешение изменения
|
||||
if (this.#block.has(parameter)) return;
|
||||
|
||||
// Инициализация значения параметра
|
||||
const value = this.#shell.getAttribute(attribute);
|
||||
|
||||
if (typeof value !== undefined || typeof value !== null) {
|
||||
// Найдено значение
|
||||
|
||||
// Инициализация буфера для временных данных
|
||||
let buffer;
|
||||
|
||||
// Запись параметра
|
||||
this[parameter] = isNaN(buffer = parseFloat(value))
|
||||
? value === "true" ? true : value === "false" ? false : value
|
||||
: buffer;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
offset(value) {
|
||||
// Запись отступа
|
||||
this.#shell.firstElementChild.style[
|
||||
this.vertical ? "marginTop" : "marginLeft"
|
||||
] = value + "px";
|
||||
|
||||
if (this.events.get("offset")) {
|
||||
// Запрошен вызов события: "сдвиг"
|
||||
|
||||
// Вызов события: "сдвиг"
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(`hotline.${this.#id}.offset`, {
|
||||
detail: {
|
||||
to: value,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
static preprocessing(event = false) {
|
||||
// Инициализация счётчиков инстанций горячей строки
|
||||
const success = new Set();
|
||||
let error = 0;
|
||||
|
||||
for (
|
||||
const element of document.querySelectorAll('*[data-hotline="true"]')
|
||||
) {
|
||||
// Перебор элементов для инициализации бегущих строк
|
||||
|
||||
if (typeof element.id === "string") {
|
||||
// Найден идентификатор
|
||||
|
||||
// Инициализация инстанции бегущей строки
|
||||
const hotline = new this(element.id, element);
|
||||
|
||||
for (const attribute of element.getAttributeNames()) {
|
||||
// Перебор аттрибутов
|
||||
|
||||
// Запись параметра в инстанцию бегущей строки
|
||||
hotline.configure(attribute);
|
||||
}
|
||||
|
||||
// Запуск бегущей строки
|
||||
hotline.start();
|
||||
|
||||
// Запись инстанции бегущей строки в элемент
|
||||
element.hotline = hotline;
|
||||
|
||||
// Запись в счётчик успешных инициализаций
|
||||
success.add(hotline);
|
||||
} else ++error;
|
||||
}
|
||||
|
||||
if (event) {
|
||||
// Запрошен вызов события: "предварительная подготовка"
|
||||
|
||||
// Вызов события: "предварительная подготовка"
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(`hotline.preprocessed`, {
|
||||
detail: {
|
||||
success,
|
||||
error,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,11 +30,11 @@ export default class account {
|
|||
* @return {void}
|
||||
*/
|
||||
static authentication() {
|
||||
core.loading.removeAttribute("disabled");
|
||||
core.status_loading.removeAttribute("disabled");
|
||||
|
||||
const timer_for_response = setTimeout(() => {
|
||||
core.loading.setAttribute("disabled", true);
|
||||
}, 200);
|
||||
core.status_loading.setAttribute("disabled", true);
|
||||
}, 3000);
|
||||
|
||||
core.modules.connect("telegram").then(() => {
|
||||
// Imported the telegram module
|
||||
|
@ -44,7 +44,6 @@ export default class account {
|
|||
.request(
|
||||
"/session/connect/telegram",
|
||||
core.telegram.api.initData,
|
||||
"PUT",
|
||||
)
|
||||
.then((json) => {
|
||||
if (json) {
|
||||
|
@ -55,74 +54,25 @@ export default class account {
|
|||
typeof json.errors === "object" &&
|
||||
json.errors.length > 0
|
||||
) {
|
||||
// Fail (received errors)
|
||||
|
||||
// Exit (fail)
|
||||
reject(json);
|
||||
// Errors received
|
||||
} else {
|
||||
// Success (not received errors)
|
||||
// Errors not received
|
||||
|
||||
if (json.connected === true) {
|
||||
// Deactivating the loading screen
|
||||
core.loading.setAttribute("disabled", true);
|
||||
core.status_loading.setAttribute("disabled", true);
|
||||
clearTimeout(timer_for_response);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
const a = core.status_account.getElementsByTagName("a")[0];
|
||||
a.setAttribute("onclick", "core.account.profile()");
|
||||
a.innerText = json.domain.length > 0
|
||||
? "@" + json.domain
|
||||
: "ERROR";
|
||||
}
|
||||
|
||||
if (
|
||||
json.language !== null &&
|
||||
typeof json.language === "string" &&
|
||||
json.language.length === 2
|
||||
json.langiage.length === 2
|
||||
) {
|
||||
core.language = json.language;
|
||||
}
|
||||
|
@ -147,7 +97,7 @@ export default class account {
|
|||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Did the execution complete without errors?
|
||||
* @return {bool} Execution completed with an error?
|
||||
*/
|
||||
static write = (name, value, force = false) => {
|
||||
core.modules.connect("damper").then(
|
||||
|
@ -166,7 +116,7 @@ export default class account {
|
|||
);
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -178,12 +128,10 @@ core.modules.connect("damper").then(() => {
|
|||
account.buffer.write,
|
||||
{
|
||||
/**
|
||||
* @name Write (damper)
|
||||
* @name Write
|
||||
*
|
||||
* @description
|
||||
* Write to the account buffer
|
||||
*
|
||||
* @memberof account.buffer.write
|
||||
* Write to the account buffer (damper)
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
|
@ -194,7 +142,7 @@ core.modules.connect("damper").then(() => {
|
|||
damper: core.damper(
|
||||
(...variables) => account.buffer.write.system(...variables),
|
||||
300,
|
||||
2,
|
||||
3,
|
||||
),
|
||||
},
|
||||
);
|
||||
|
@ -204,12 +152,10 @@ Object.assign(
|
|||
account.buffer.write,
|
||||
{
|
||||
/**
|
||||
* @name Write (system)
|
||||
* @name Write
|
||||
*
|
||||
* @description
|
||||
* Write to the account buffer
|
||||
*
|
||||
* @memberof account.buffer.write
|
||||
* Write to the account buffer (system)
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
|
@ -223,38 +169,22 @@ Object.assign(
|
|||
reject = () => {},
|
||||
) {
|
||||
try {
|
||||
if (
|
||||
typeof name === "string" &&
|
||||
(typeof value === "string" || typeof value === "number")
|
||||
) {
|
||||
if (typeof name === "string" && typeof value === "string") {
|
||||
// Received and validated required arguments
|
||||
|
||||
// Sending request to the server
|
||||
return await core.request(
|
||||
"/account/write",
|
||||
`${name}=${value}`,
|
||||
"PATCH",
|
||||
"POST",
|
||||
)
|
||||
.then(
|
||||
(json) => {
|
||||
if (json) {
|
||||
// Received a JSON-response
|
||||
|
||||
if (
|
||||
json.errors !== null &&
|
||||
typeof json.errors === "object" &&
|
||||
json.errors.length > 0
|
||||
) {
|
||||
// Fail (received errors)
|
||||
|
||||
// Exit (fail)
|
||||
reject(json);
|
||||
} else {
|
||||
// Success (not received errors)
|
||||
|
||||
// Exit (success)
|
||||
resolve(json);
|
||||
}
|
||||
// Exit (success)
|
||||
resolve(json);
|
||||
}
|
||||
},
|
||||
() => reject(),
|
|
@ -0,0 +1,528 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* @name Cart
|
||||
*
|
||||
* @description
|
||||
* Implements actions with cart
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
export default class cart {
|
||||
/**
|
||||
* @name Type of the program
|
||||
*/
|
||||
static type = "module";
|
||||
|
||||
/**
|
||||
* Toggle
|
||||
*
|
||||
* Toggle the product in the cart (interface)
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
|
||||
* @param {HTMLElement} product The product element
|
||||
* @param {bool} remove Remove the product element if json.amount === 0?
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Execution completed with an error?
|
||||
*/
|
||||
static toggle(element, product, remove = false, force = false) {
|
||||
// Blocking the element
|
||||
element.setAttribute("disabled", "true");
|
||||
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.product.damper(
|
||||
element,
|
||||
product,
|
||||
"toggle",
|
||||
undefined,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.product.system(
|
||||
element,
|
||||
product,
|
||||
"toggle",
|
||||
undefined,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write
|
||||
*
|
||||
* Write the product in the cart (interface)
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
|
||||
* @param {HTMLElement} product The product element
|
||||
* @param {number} amount Amount of writings
|
||||
* @param {bool} remove Remove the product element if json.amount === 0?
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Execution completed with an error?
|
||||
*/
|
||||
static write(
|
||||
element,
|
||||
product,
|
||||
amount = 1,
|
||||
remove = false,
|
||||
force = false,
|
||||
) {
|
||||
// Blocking the element
|
||||
element.setAttribute("disabled", "true");
|
||||
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.product.damper(
|
||||
element,
|
||||
product,
|
||||
"write",
|
||||
amount,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.product.system(
|
||||
element,
|
||||
product,
|
||||
"write",
|
||||
amount,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete
|
||||
*
|
||||
* Delete the product from the cart (interface)
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
|
||||
* @param {HTMLElement} product The product element
|
||||
* @param {number} amount Amount of deletings
|
||||
* @param {bool} remove Remove the product element if json.amount === 0?
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Execution completed with an error?
|
||||
*/
|
||||
static delete(
|
||||
element,
|
||||
product,
|
||||
amount = 1,
|
||||
remove = false,
|
||||
force = false,
|
||||
) {
|
||||
// Blocking the element
|
||||
element.setAttribute("disabled", "true");
|
||||
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.product.damper(
|
||||
element,
|
||||
product,
|
||||
"delete",
|
||||
amount,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.product.system(
|
||||
element,
|
||||
product,
|
||||
"delete",
|
||||
amount,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set
|
||||
*
|
||||
* Set amount of the product in the cart (interface)
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
|
||||
* @param {HTMLElement} product The product element
|
||||
* @param {number} amount Amount of the product in the cart to be setted
|
||||
* @param {bool} remove Remove the product element if json.amount === 0?
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Execution completed with an error?
|
||||
*/
|
||||
static set(
|
||||
element,
|
||||
product,
|
||||
amount = 1,
|
||||
remove = false,
|
||||
force = false,
|
||||
) {
|
||||
// Blocking the element
|
||||
element.setAttribute("disabled", "true");
|
||||
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.product.damper(
|
||||
element,
|
||||
product,
|
||||
"set",
|
||||
amount,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.product.system(
|
||||
element,
|
||||
product,
|
||||
"set",
|
||||
amount,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The product
|
||||
*
|
||||
* Handle the product in the cart (system)
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement} element Handler element of the product
|
||||
* @param {HTMLElement} product The product element
|
||||
* @param {string} type Type of action with the product
|
||||
* @param {number} amount Amount of product to handle
|
||||
* @param {bool} remove Remove the product element if json.amount === 0?
|
||||
*
|
||||
* @return {bool} Execution completed with an error?
|
||||
*/
|
||||
static product(
|
||||
element,
|
||||
product,
|
||||
type,
|
||||
amount = null,
|
||||
remove = false,
|
||||
) {
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.product.damper(
|
||||
element,
|
||||
product,
|
||||
type,
|
||||
amount,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.product.system(
|
||||
element,
|
||||
product,
|
||||
type,
|
||||
amount,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Summary
|
||||
*
|
||||
* @description
|
||||
* Initialize summary of products the cart (system)
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async summary() {
|
||||
// Request
|
||||
return await core.request("/cart/summary")
|
||||
.then((json) => {
|
||||
if (json) {
|
||||
// Received a JSON-response
|
||||
|
||||
if (
|
||||
json.errors !== null &&
|
||||
typeof json.errors === "object" &&
|
||||
json.errors.length > 0
|
||||
) {
|
||||
// Fail (received errors)
|
||||
} else {
|
||||
// Success (not received errors)
|
||||
|
||||
// Initializing the summary amount <span> element
|
||||
const amount = document.getElementById("amount");
|
||||
|
||||
if (amount instanceof HTMLElement) {
|
||||
// Initialized the summary amount element
|
||||
|
||||
// Writing summmary amount into the summary amount element
|
||||
amount.innerText = json.amount;
|
||||
}
|
||||
|
||||
// Initializing the summary cost <span> element
|
||||
const cost = document.getElementById("cost");
|
||||
|
||||
if (cost instanceof HTMLElement) {
|
||||
// Initialized the summary cost element
|
||||
|
||||
// Writing summmary cost into the summary cost element
|
||||
cost.innerText = json.cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
core.modules.connect("damper").then(() => {
|
||||
// Imported the damper module
|
||||
|
||||
Object.assign(
|
||||
cart.product,
|
||||
{
|
||||
/**
|
||||
* Toggle
|
||||
*
|
||||
* Toggle the product in the cart (damper)
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
|
||||
* @param {HTMLElement} product The product element
|
||||
* @param {bool} remove Remove the product element if json.amount === 0?
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
damper: core.damper(
|
||||
(...variables) => cart.product.system(...variables).then(cart.summary),
|
||||
300,
|
||||
6,
|
||||
),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Object.assign(
|
||||
cart.product,
|
||||
{
|
||||
/**
|
||||
* The product
|
||||
*
|
||||
* Handle the product in the cart (system)
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement} element Handler element of the product
|
||||
* @param {HTMLElement} product The product element
|
||||
* @param {string} type Type of action with the product
|
||||
* @param {number} amount Amount of product to handle
|
||||
* @param {bool} remove Remove the product element if json.amount === 0?
|
||||
*
|
||||
* @return {Promise|null}
|
||||
*/
|
||||
async system(
|
||||
element,
|
||||
product,
|
||||
type,
|
||||
amount = null,
|
||||
remove = false,
|
||||
resolve = () => {},
|
||||
reject = () => {},
|
||||
) {
|
||||
try {
|
||||
if (
|
||||
(element instanceof HTMLButtonElement ||
|
||||
element instanceof HTMLInputElement) &&
|
||||
product instanceof HTMLElement
|
||||
) {
|
||||
// Validated
|
||||
|
||||
// Initializing the buffer of request body
|
||||
let request = "";
|
||||
|
||||
// Initializing of identifier of the product
|
||||
const identifier = +product.getAttribute(
|
||||
"data-product-identifier",
|
||||
);
|
||||
|
||||
if (typeof identifier === "number") {
|
||||
// Validated identifier
|
||||
|
||||
// Writing to the buffer of request body
|
||||
request += "&identifier=" + identifier;
|
||||
|
||||
if (
|
||||
type === "toggle" ||
|
||||
type === "write" ||
|
||||
type === "delete" ||
|
||||
type === "set"
|
||||
) {
|
||||
// Validated type
|
||||
|
||||
// Writing to the buffer of request body
|
||||
request += "&type=" + type;
|
||||
|
||||
console.log(type, amount);
|
||||
if (
|
||||
(type === "toggle" &&
|
||||
amount === null ||
|
||||
typeof amount === "undefined") ||
|
||||
(type === "set" &&
|
||||
amount === 0 ||
|
||||
amount === 100) ||
|
||||
typeof amount === "number" &&
|
||||
amount > 0 &&
|
||||
amount < 100
|
||||
) {
|
||||
// Validated amount
|
||||
|
||||
console.log(amount);
|
||||
if (type !== "toggle") {
|
||||
// Not a toggle request
|
||||
|
||||
// Writing to the buffer of request body
|
||||
request += "&amount=" + amount;
|
||||
}
|
||||
|
||||
// Request
|
||||
return await core.request(
|
||||
"/cart/product",
|
||||
request,
|
||||
)
|
||||
.then(
|
||||
(json) => {
|
||||
if (json) {
|
||||
// Received a JSON-response
|
||||
|
||||
if (
|
||||
json.errors !== null &&
|
||||
typeof json.errors === "object" &&
|
||||
json.errors.length > 0
|
||||
) {
|
||||
// Fail (received errors)
|
||||
} else {
|
||||
// Success (not received errors)
|
||||
|
||||
if (remove && json.amount === 0) {
|
||||
// Requested deleting of the product element when there is no the product in the cart
|
||||
|
||||
// Deleting the product element
|
||||
product.remove();
|
||||
} else {
|
||||
// Not requested deleting the product element when there is no the product in the cart
|
||||
|
||||
// Unblocking the element
|
||||
element.removeAttribute("disabled");
|
||||
|
||||
// Writing offset of hue-rotate to indicate that the product is in the cart
|
||||
product.style.setProperty(
|
||||
"--hue-rotate-offset",
|
||||
json.amount + "0deg",
|
||||
);
|
||||
|
||||
// Writing attribute with amount of the product in the cart
|
||||
product.setAttribute(
|
||||
"data-product-amount",
|
||||
json.amount,
|
||||
);
|
||||
|
||||
// Initializing the amount <span> element
|
||||
const amounts = product.querySelectorAll(
|
||||
'[data-product-parameter="amount"]',
|
||||
);
|
||||
|
||||
for (const amount of amounts) {
|
||||
// Iterating over an amount elements
|
||||
|
||||
if (amount instanceof HTMLInputElement) {
|
||||
// The <input> element
|
||||
|
||||
// Writing amount of the product in the cart
|
||||
amount.value = json.amount;
|
||||
} else {
|
||||
// Not the <input> element
|
||||
|
||||
// Writing amount of the product in the cart
|
||||
amount.innerText = json.amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
},
|
||||
() => reject(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Exit (fail)
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Connecting to the core
|
||||
if (!core.cart) core.cart = cart;
|
File diff suppressed because it is too large
Load Diff
|
@ -49,10 +49,7 @@ export default function damper(func, timeout = 300, force) {
|
|||
// Requested execution with ignoring the timer
|
||||
|
||||
// Deleting the force argument
|
||||
if (typeof force === "number") args = [
|
||||
...args.splice(0, force),
|
||||
...args.splice(force + 1)
|
||||
];
|
||||
if (typeof force === "number") delete args[force - 1];
|
||||
|
||||
// Writing promise handlers into the arguments variable
|
||||
args.push(resolve, reject);
|
||||
|
@ -63,10 +60,7 @@ export default function damper(func, timeout = 300, force) {
|
|||
// Normal execution
|
||||
|
||||
// Deleting the force argument
|
||||
if (typeof force === "number") args = [
|
||||
...args.splice(0, force),
|
||||
...args.splice(force + 1)
|
||||
];
|
||||
if (typeof force === "number") delete args[force - 1];
|
||||
|
||||
// Writing promise handlers into the arguments variable
|
||||
args.push(resolve, reject);
|
|
@ -0,0 +1,161 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* @name Delivery
|
||||
*
|
||||
* @description
|
||||
* Implements actions with delivery
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
export default class delivery {
|
||||
/**
|
||||
* @name Type of the program
|
||||
*/
|
||||
static type = "module";
|
||||
|
||||
/**
|
||||
* @name Company
|
||||
*
|
||||
* @description
|
||||
* Choose a delivery company (interface)
|
||||
*
|
||||
* @param {HTNLElement} element Handler element
|
||||
* @param {string} identifier Identifier of the delivery company
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Execution completed with an error?
|
||||
*/
|
||||
static company = (element, identifier, force = false) => {
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.company.damper(element, identifier, force);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.company.system(element, identifier, force);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @name City
|
||||
*
|
||||
* @description
|
||||
* Write name of a city for delivery (interface)
|
||||
*
|
||||
* @param {HTNLInputElement} element Handler element <input>
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Execution completed with an error?
|
||||
*/
|
||||
static company = (element, force = false) => {
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.city.damper(element, identifier, force);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.company.system(element, identifier, force);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
core.modules.connect("damper").then(() => {
|
||||
// Imported the damper module
|
||||
|
||||
Object.assign(
|
||||
delivery.company,
|
||||
{
|
||||
/**
|
||||
* @name Write
|
||||
*
|
||||
* @description
|
||||
* Choose a delivery company (damper)
|
||||
*
|
||||
* @param {HTNLElement} element Handler element
|
||||
* @param {string} identifier Identifier of the delivery company
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
damper: core.damper(
|
||||
(...variables) => delivery.company.system(...variables),
|
||||
300,
|
||||
3,
|
||||
),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Object.assign(
|
||||
delivery.company,
|
||||
{
|
||||
/**
|
||||
* @name Write
|
||||
*
|
||||
* @description
|
||||
* Choose a delivery company (system)
|
||||
*
|
||||
* @param {string} identifier Identifier of the delivery company
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
async system(
|
||||
type,
|
||||
value,
|
||||
resolve = () => {},
|
||||
reject = () => {},
|
||||
) {
|
||||
try {
|
||||
if (
|
||||
typeof type === "string" &&
|
||||
(typeof value === "string" || typeof valye === "number")
|
||||
) {
|
||||
// Received and validated required arguments
|
||||
|
||||
// Sending request to the server
|
||||
return await core.buffer.write(`delivery_${type}`, value)
|
||||
.then(
|
||||
(json) => {
|
||||
try {
|
||||
|
||||
|
||||
// Exit (success)
|
||||
resolve(json);
|
||||
} catch (e) {
|
||||
// Exit (fail)
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
() => reject(),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// Exit (fail)
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Connecting to the core
|
||||
if (!core.delivery) core.delivery = delivery;
|
|
@ -15,11 +15,6 @@ export default class loader {
|
|||
*/
|
||||
static type = "module";
|
||||
|
||||
/**
|
||||
* @name Actial URI
|
||||
*/
|
||||
static uri;
|
||||
|
||||
/**
|
||||
* @name Load
|
||||
*
|
||||
|
@ -28,7 +23,7 @@ export default class loader {
|
|||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async load(uri = "/", body, back = false) {
|
||||
static async load(uri = "/", body) {
|
||||
if (typeof uri === "string") {
|
||||
// Received and validated uri
|
||||
|
||||
|
@ -36,7 +31,7 @@ export default class loader {
|
|||
.request(
|
||||
uri,
|
||||
body,
|
||||
"GET",
|
||||
"POST",
|
||||
{
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "application/json",
|
||||
|
@ -53,33 +48,11 @@ export default class loader {
|
|||
json.errors.length > 0
|
||||
) {
|
||||
// Fail (received errors)
|
||||
|
||||
// Exit (fail)
|
||||
reject(json);
|
||||
} else {
|
||||
// Success (not received errors)
|
||||
|
||||
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;
|
||||
}
|
||||
// Writing to the browser history
|
||||
history.pushState({}, json.title ?? uri, uri);
|
||||
|
||||
/**
|
||||
* The <title>
|
||||
|
@ -174,7 +147,7 @@ export default class loader {
|
|||
// Writing the <header> element after the last <section> element inside the <body> element
|
||||
body.insertBefore(
|
||||
core.header,
|
||||
section.nextElementSibling,
|
||||
section.nextSibling,
|
||||
);
|
||||
} else {
|
||||
// Not found section elements <section> inside the <body> element
|
||||
|
@ -227,7 +200,7 @@ export default class loader {
|
|||
// Writing the <main> element after the <header> element
|
||||
body.insertBefore(
|
||||
core.main,
|
||||
core.header.nextElementSibling,
|
||||
core.header.nextSibling,
|
||||
);
|
||||
} else if (core.footer instanceof HTMLElement) {
|
||||
// Fount the <footer> element
|
||||
|
@ -246,10 +219,7 @@ 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.nextSibling);
|
||||
} else {
|
||||
// Not found section elements <section> inside the <body> element
|
||||
|
|
@ -29,7 +29,7 @@ export default class session {
|
|||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Did the execution complete without errors?
|
||||
* @return {bool} Execution completed with an error?
|
||||
*/
|
||||
static write = (name, value, force = false) => {
|
||||
core.modules.connect("damper").then(
|
||||
|
@ -48,7 +48,7 @@ export default class session {
|
|||
);
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -60,12 +60,10 @@ core.modules.connect("damper").then(() => {
|
|||
session.buffer.write,
|
||||
{
|
||||
/**
|
||||
* @name Write (damper)
|
||||
* @name Write
|
||||
*
|
||||
* @description
|
||||
* Write to the session buffer
|
||||
*
|
||||
* @memberof session.buffer.write
|
||||
* Write to the session buffer (damper)
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
|
@ -76,7 +74,7 @@ core.modules.connect("damper").then(() => {
|
|||
damper: core.damper(
|
||||
(...variables) => session.buffer.write.system(...variables),
|
||||
300,
|
||||
2,
|
||||
3,
|
||||
),
|
||||
},
|
||||
);
|
||||
|
@ -86,12 +84,10 @@ Object.assign(
|
|||
session.buffer.write,
|
||||
{
|
||||
/**
|
||||
* @name Write (system)
|
||||
* @name Write
|
||||
*
|
||||
* @description
|
||||
* Write to the session buffer
|
||||
*
|
||||
* @memberof session.buffer.write
|
||||
* Write to the session buffer (system)
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
|
@ -104,38 +100,22 @@ Object.assign(
|
|||
resolve = () => {},
|
||||
reject = () => {},
|
||||
) {
|
||||
if (
|
||||
typeof name === "string" &&
|
||||
(typeof value === "string" || typeof value === "number")
|
||||
) {
|
||||
if (typeof name === "string" && typeof value === "string") {
|
||||
// Received and validated required arguments
|
||||
|
||||
// Sending request to the server
|
||||
return await core.request(
|
||||
"/session/write",
|
||||
`${name}=${value}`,
|
||||
"PATCH",
|
||||
"POST",
|
||||
)
|
||||
.then(
|
||||
(json) => {
|
||||
if (json) {
|
||||
// Received a JSON-response
|
||||
|
||||
if (
|
||||
json.errors !== null &&
|
||||
typeof json.errors === "object" &&
|
||||
json.errors.length > 0
|
||||
) {
|
||||
// Fail (received errors)
|
||||
|
||||
// Exit (fail)
|
||||
reject(json);
|
||||
} else {
|
||||
// Success (not received errors)
|
||||
|
||||
// Exit (success)
|
||||
resolve(json);
|
||||
}
|
||||
// Exit (success)
|
||||
resolve(json);
|
||||
}
|
||||
},
|
||||
() => reject(),
|
||||
|
@ -146,4 +126,4 @@ Object.assign(
|
|||
);
|
||||
|
||||
// Connecting to the core
|
||||
// if (!core.session) core.session = session;
|
||||
if (!core.session) core.session = session;
|
|
@ -21,11 +21,6 @@ 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
|
|
@ -0,0 +1,92 @@
|
|||
<?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\telegram;
|
||||
|
||||
// Фреймворк Telegram
|
||||
use Zanzara\Zanzara,
|
||||
Zanzara\Context,
|
||||
Zanzara\Config;
|
||||
|
||||
ini_set('error_reporting', E_ALL);
|
||||
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(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 {
|
||||
var_dump($ctx->getMessage()->getWebAppData());
|
||||
var_dump($ctx->getEffectiveUser() );
|
||||
}); */
|
||||
|
||||
$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(['tuning'], fn($ctx) => telegram::tuning($ctx));
|
||||
$bot->onCbQueryData(['brands'], fn($ctx) => telegram::brands($ctx));
|
||||
|
||||
// Инициализация middleware с обработкой аккаунта
|
||||
$bot->middleware([telegram::class, "account"]);
|
||||
|
||||
// Инициализация middleware с обработкой технических работ разных уровней
|
||||
$bot->middleware([telegram::class, "suspension"]);
|
||||
|
||||
// Запуск чат-робота
|
||||
$bot->run();
|
8
mirzaev/huesos/system/public/socket.php → mirzaev/arming_bot/system/public/socket.php
Executable file → Normal file
8
mirzaev/huesos/system/public/socket.php → mirzaev/arming_bot/system/public/socket.php
Executable file → Normal file
|
@ -2,12 +2,12 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos;
|
||||
namespace mirzaev\arming_bot;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\controllers\core as controller,
|
||||
mirzaev\huesos\models\core as model,
|
||||
mirzaev\huesos\models\socket;
|
||||
use mirzaev\arming_bot\controllers\core as controller,
|
||||
mirzaev\arming_bot\models\core as model,
|
||||
mirzaev\arming_bot\models\socket;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\core;
|
|
@ -0,0 +1,11 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
section#account {
|
||||
z-index: 999;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
}
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
main>section:is(#summary, #products, #delivery) {
|
||||
width: var(--width);
|
||||
gap: var(--gap);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column {
|
||||
padding: 1rem;
|
||||
gap: var(--gap);
|
||||
background-color: var(--tg-theme-secondary-bg-color);
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#deliveries>input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#deliveries>input+label {
|
||||
/* backdrop-filter: brightness(1.2); */
|
||||
/* background-color: unset; */
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#deliveries>input+label:is(:hover, :focus) {
|
||||
filter: brightness(1.1) hue-rotate(30deg);
|
||||
/* filter: unset; */
|
||||
/* backdrop-filter: brightness(1.4); */
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#deliveries>input+label:active {
|
||||
filter: unset;
|
||||
/* backdrop-filter: brightness(0.8); */
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#deliveries>input:checked+label {
|
||||
filter: hue-rotate(30deg);
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#deliveries>input:checked+label:is(:hover, :focus) {
|
||||
filter: brightness(1.1) hue-rotate(30deg);
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#deliveries>input:checked+label:active {
|
||||
filter: brightness(0.9) hue-rotate(30deg);
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#address {
|
||||
flex-flow: row wrap;
|
||||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#address>input#city {
|
||||
min-width: max(6rem, 20%);
|
||||
width: min(6rem, 100%);
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#address>input#street {
|
||||
min-width: max(10rem, 30%);
|
||||
width: min(10rem, 100%);
|
||||
flex-grow: 10;
|
||||
}
|
||||
|
||||
main:has(section#summary.disabled:hover)>section#delivery>div {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
main>section#summary>div {
|
||||
container-type: inline-size;
|
||||
container-name: summary;
|
||||
padding-left: 1rem;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
overflow: hidden;
|
||||
pointer-events: auto;
|
||||
border-radius: 1.375rem;
|
||||
background-color: var(--tg-theme-secondary-bg-color);
|
||||
}
|
||||
|
||||
main>section#summary>div>span {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
main>section#summary>div>span:first-of-type {
|
||||
/* margin-left: auto; */
|
||||
}
|
||||
|
||||
main>section#summary>div>button#order {
|
||||
/* margin-left: 0.3rem; */
|
||||
margin-left: auto;
|
||||
padding-left: 0.7rem;
|
||||
}
|
||||
|
||||
main>section#summary.disabled,
|
||||
main>section#summary>div:has(button#order:disabled),
|
||||
main:not(:has(section#delivery>div.column>div#deliveries>input:checked))>section#summary>div:has(button#order:enabled) {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
main>section#summary.disabled,
|
||||
main>section#summary>div>button#order:disabled,
|
||||
main:not(:has(section#delivery>div.column>div#deliveries>input:checked))>section#summary>div>button#order:enabled {
|
||||
pointer-events: none;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
main>section#products>article.product {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 5rem;
|
||||
max-height: 10rem;
|
||||
display: flex;
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
background-color: var(--tg-theme-secondary-bg-color);
|
||||
}
|
||||
|
||||
main>section#products>article.product[data-product-amount]:not([data-product-amount="0"]) * {
|
||||
backdrop-filter: hue-rotate(calc(120deg + var(--hue-rotate-offset, 0deg)));
|
||||
}
|
||||
|
||||
main>section#products>article.product:is(:hover, :focus)>* {
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
main>section#products>article.product:not(:is(:hover, :focus))>* {
|
||||
transition: 0.2s ease-out;
|
||||
}
|
||||
|
||||
main>section#products>article.product>a {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
main>section#products>article.product>a>img:first-of-type {
|
||||
width: 5rem;
|
||||
min-width: 5rem;
|
||||
min-height: 100%;
|
||||
object-fit: cover;
|
||||
image-rendering: auto;
|
||||
}
|
||||
|
||||
main>section#products>article.product>div {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
main>section#products>article.product>div>div {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
main>section#products>article.product>div>div.head {
|
||||
z-index: 50;
|
||||
padding: 0 0.4rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
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;
|
||||
background: unset;
|
||||
}
|
||||
|
||||
main>section#products>article.product>div>div.head>button+button {
|
||||
margin-left: 0.4rem;
|
||||
}
|
||||
|
||||
main>section#products>article.product>div>div.head>button>i.icon.trash {
|
||||
color: var(--tg-theme-destructive-text-color);
|
||||
}
|
||||
|
||||
main>section#products>article.product>div>div.body {
|
||||
z-index: 30;
|
||||
flex-grow: 1;
|
||||
display: inline-flex;
|
||||
flex-flow: row wrap;
|
||||
align-items: start;
|
||||
gap: 0.3rem;
|
||||
font-size: 0.6rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
main>section#products>article.product>div>div.body>span {
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 0.75rem;
|
||||
color: var(--tg-theme-button-text-color);
|
||||
background-color: var(--tg-theme-button-color);
|
||||
}
|
||||
|
||||
main>section#products>article.product>div>div.footer {
|
||||
z-index: 100;
|
||||
padding: 0 0.4rem;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
main>section#products>article.product>div>div.footer>span[data-product-parameter] {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
main>section#products>article.product>div>div.footer>span[data-product-parameter]+span[data-product-parameter="currency"] {
|
||||
margin-left: 0.1rem;
|
||||
}
|
||||
|
||||
main>section#products>article.product>div>div.footer>input {
|
||||
width: 2rem;
|
||||
padding: 0 0.3rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@container summary (max-width: 350px) {
|
||||
main>section#summary>div>span:not(#cost) {
|
||||
display: none;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
@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-secondary-bg-color);
|
||||
}
|
||||
|
||||
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;
|
||||
backdrop-filter: brightness(0.7);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product:is(:hover, :focus) {
|
||||
/* flex-grow: 0.1; */
|
||||
/* background-color: var(--tg-theme-secondary-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 {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product>a>img:first-of-type {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
image-rendering: auto;
|
||||
}
|
||||
|
||||
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;
|
||||
font-size: 0.9rem;
|
||||
font-weight: bold;
|
||||
overflow-wrap: anywhere;
|
||||
hyphens: auto;
|
||||
color: var(--tg-theme-text-color);
|
||||
background-color: var(--tg-theme-secondary-bg-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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ i.icon.arrow:not(.circle, .square)::after {
|
|||
bottom: 7px;
|
||||
}
|
||||
|
||||
i.icon.arrow:not(.circle, .square, .short)::before {
|
||||
i.icon.arrow:not(.circle, .square)::before {
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
bottom: 10px;
|
|
@ -0,0 +1,30 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
i.icon.close {
|
||||
--diameter: 22px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
display: block;
|
||||
width: var(--diameter);
|
||||
height: var(--diameter);
|
||||
border: 2px solid transparent;
|
||||
border-radius: 40px;
|
||||
}
|
||||
i.icon.close::after,
|
||||
i.icon.close::before {
|
||||
content: "";
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
background: currentColor;
|
||||
transform: rotate(45deg);
|
||||
border-radius: 5px;
|
||||
top: 8px;
|
||||
left: 1px;
|
||||
}
|
||||
i.icon.close::after {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
@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: context-menu;
|
||||
border-radius: 0.75rem;
|
||||
overflow-x: hidden;
|
||||
background-color: var(--tg-theme-button-color);
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
section[data-type="select"]:not(:focus):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%
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
:root {
|
||||
--text-light: #fafaff;
|
||||
|
||||
--socket-connected: #2be851;
|
||||
--socket-disconnected: #8e8181;
|
||||
--socket-text: #b09999;
|
||||
}
|
||||
|
||||
* {
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
border: none;
|
||||
font-family: "DejaVu";
|
||||
color: var(--tg-theme-text-color);
|
||||
transition: 0.1s ease-out;
|
||||
}
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
color: var(--tg-theme-link-color);
|
||||
}
|
||||
|
||||
body {
|
||||
--gap: 16px;
|
||||
--width: calc(100% - var(--gap) * 2);
|
||||
--offset-x: 2%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: clip;
|
||||
background-color: var(--tg-theme-bg-color);
|
||||
}
|
||||
|
||||
|
||||
aside {}
|
||||
|
||||
header {
|
||||
container-type: inline-size;
|
||||
container-name: header;
|
||||
margin: 2rem 0 1rem;
|
||||
padding: 0 var(--offset-x);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 26px;
|
||||
}
|
||||
|
||||
main {
|
||||
container-type: inline-size;
|
||||
container-name: main;
|
||||
padding: 0 var(--offset-x);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 26px;
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
main>section:last-child {
|
||||
margin-bottom: 5rem;
|
||||
}
|
||||
|
||||
main>*[data-section] {
|
||||
width: var(--width);
|
||||
}
|
||||
|
||||
main>section[data-section]>p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
main>search {
|
||||
--gap: 16px;
|
||||
--border-width: 1px;
|
||||
width: var(--width);
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
border-radius: 1.375rem;
|
||||
backdrop-filter: contrast(0.8);
|
||||
border: 2px solid transparent;
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
footer {}
|
||||
|
||||
search:has(input:is(:focus, :active)) {
|
||||
border-color: var(--tg-theme-accent-text-color);
|
||||
transition: unset;
|
||||
}
|
||||
|
||||
search>label {
|
||||
margin-inline-start: 0.75rem;
|
||||
width: 1.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
search>label>i.icon {
|
||||
color: var(--tg-theme-subtitle-text-color);
|
||||
}
|
||||
|
||||
search:has(input:is(:focus, :active))>label>i.icon {
|
||||
color: var(--tg-theme-accent-text-color);
|
||||
transition: unset;
|
||||
}
|
||||
|
||||
search>input {
|
||||
width: 100%;
|
||||
max-width: calc(100% - 3.25rem);
|
||||
height: 2.5rem;
|
||||
touch-action: manipulation;
|
||||
padding: calc(.4375rem - var(--border-width)) calc(.625rem - var(--border-width)) calc(.5rem - var(--border-width)) calc(.75rem - var(--border-width));
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
search>input:disabled {
|
||||
cursor: progress;
|
||||
color: var(--tg-theme-subtitle-text-color);
|
||||
}
|
||||
|
||||
search:has(input:disabled) {
|
||||
backdrop-filter: contrast(0.5);
|
||||
}
|
||||
|
||||
button,
|
||||
*[type="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:is(button, :is(a, label)[type="button"]) {
|
||||
padding: 8px 16px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--tg-theme-button-text-color);
|
||||
background-color: var(--tg-theme-button-color);
|
||||
}
|
||||
|
||||
button {
|
||||
height: 33px;
|
||||
color: var(--tg-theme-button-text-color);
|
||||
background-color: var(--tg-theme-button-color);
|
||||
}
|
||||
|
||||
a[type="button"] {
|
||||
height: 23px;
|
||||
}
|
||||
|
||||
:is(button, :is(a, label)[type="button"]):is(:hover, :focus) {
|
||||
filter: brightness(120%);
|
||||
}
|
||||
|
||||
:is(button, :is(a, label)[type="button"]):active {
|
||||
filter: brightness(80%);
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
margin: 1rem 0 0;
|
||||
}
|
||||
|
||||
input {
|
||||
background: unset;
|
||||
}
|
||||
|
||||
.kabrio {
|
||||
font-family: "Kabrio";
|
||||
}
|
||||
|
||||
.cost.currency:after {
|
||||
content: var(--currency);
|
||||
margin-left: var(--currency-offset, 0.1rem);
|
||||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.unselectable {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
@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;
|
||||
}
|
||||
}
|
|
@ -8,18 +8,7 @@ section#window {
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
/* backdrop-filter: brightness(40%) contrast(120%) grayscale(60%) blur(1.2px); */
|
||||
}
|
||||
|
||||
section#window:before {
|
||||
content: '';
|
||||
z-index: -100;
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
opacity: 0.7;
|
||||
background: #000;
|
||||
filter: brightness(50%);
|
||||
backdrop-filter: brightness(50%) contrast(120%) grayscale(60%) blur(1.2px);
|
||||
}
|
||||
|
||||
section#window>div.card {
|
||||
|
@ -69,45 +58,34 @@ section#window>div.card>h3>a.exit[type="button"] {
|
|||
}
|
||||
|
||||
section#window>div.card>div.images {
|
||||
--image-width: 10rem;
|
||||
height: 10rem;
|
||||
min-height: 10rem;
|
||||
max-height: 10rem;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
overflow: clip;
|
||||
cursor: zoom-in;
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
section#window>div.card>div.images:not(.extend):has(> img:last-child:nth-child(2)) {
|
||||
padding: 0rem 1rem;
|
||||
}
|
||||
|
||||
section#window>div.card>div.images.extend {
|
||||
--image-width: max(30rem, 40vw);
|
||||
z-index: 9999999;
|
||||
left: 10vw;
|
||||
top: 10vh;
|
||||
left: 0;
|
||||
top: 0;
|
||||
margin: unset !important;
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
max-width: unset;
|
||||
height: 80vh;
|
||||
min-height: 80vh;
|
||||
max-height: 80vh;
|
||||
height: 100vh;
|
||||
align-items: center;
|
||||
cursor: default;
|
||||
overflow: unset;
|
||||
cursor: normal;
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
section#window>div.card:has(>div.images.extend)>:not(div.images.extend) {
|
||||
filter: brightness(50%);
|
||||
}
|
||||
|
||||
section#window>div.card>div.images>img {
|
||||
margin-right: 0.5rem;
|
||||
min-width: 150px;
|
||||
width: auto;
|
||||
height: 10rem;
|
||||
width: var(--image-width);
|
||||
max-width: var(--image-width);
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
object-fit: cover;
|
||||
|
@ -117,12 +95,10 @@ section#window>div.card>div.images>img {
|
|||
}
|
||||
|
||||
section#window>div.card>div.images.extend>img {
|
||||
margin-right: 3vw;
|
||||
width: 80vw;
|
||||
height: auto;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
border-radius: 0.75rem;
|
||||
margin-right: 2vw;
|
||||
height: max-content;
|
||||
object-fit: unset;
|
||||
border-radius: unset;
|
||||
}
|
||||
|
||||
section#window>div.card>div.images>img:last-child {
|
||||
|
@ -186,6 +162,7 @@ section#window>div.card>div.footer>button.buy {
|
|||
}
|
||||
|
||||
@container window-footer (max-width: 350px) {
|
||||
|
||||
section#window>div.card>div.footer>small:first-of-type {
|
||||
margin-left: auto;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue