Compare commits
No commits in common. "stable" and "0.0.0" have entirely different histories.
|
@ -1,2 +1 @@
|
|||
node_modules
|
||||
vendor
|
||||
|
|
369
README.md
369
README.md
|
@ -1,368 +1,3 @@
|
|||
# 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>
|
||||
|
||||
## 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>
|
||||
**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`
|
||||
|
||||
```json
|
||||
"indexes": [
|
||||
{
|
||||
"collection": "product",
|
||||
"index": "title_ru" # THIS IS AN EXAMPLE
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### NGINX
|
||||
|
||||
1. **Create a NGINX server**<br>
|
||||
You can copy an example of server file from here: `/examples/nginx/server.conf`
|
||||
|
||||
2. **Add support for javascript modules**<br>
|
||||
Edit the file `/etc/nginx/mime.types`<br>
|
||||
`application/javascript js;` -> `application/javascript js mjs;`
|
||||
|
||||
3. **Generate a TLS/SSL sertificate** (via [certbot](http://certbot.eff.org/) for [ubuntu](https://ubuntu.com/))<br>
|
||||
3.1. `sudo apt install certbot python3-certbot-nginx`<br>
|
||||
3.2. `sudo certbot certonly --nginx` (The **domain** must already be **bound** to the **IP-address** of the server by `A-record` or `AAAA-record`)
|
||||
|
||||
5. **Firewall rules for HTTP and HTTPS** (for [ubuntu](https://ubuntu.com/))<br>
|
||||
4.1 `sudo ufw allow "NGINX Full"`<br>
|
||||
4.1.1 `sudo ufw allow 22` (make sure that the port for SSH connection is open)<br>
|
||||
4.2 `sudo ufw enable`
|
||||
|
||||
### SystemD (or any alternative you like)
|
||||
You can copy an example of systemd file from here: `/examples/systemd/huesos.service`<br>
|
||||
1. `sudo cp huesos.service /etc/systemd/system/huesos.service && sudo chmod +x /etc/systemd/system/huesos.service`
|
||||
2. `sudo systemctl daemon-reload`
|
||||
3. `sudo systemctl enable huesos`<br>
|
||||
|
||||
*before you execute the command think about **what it does** and whether the **paths** are specified correctly*<br>
|
||||
*the configuration file is very simple and you can remake it for any alternative to SystemD that you like*
|
||||
|
||||
## Menu
|
||||
*Menu inside the Web App*<br><br>
|
||||
Make sure you have a **menu** collection (can be created automatically)<br>
|
||||
You can copy a clean menu documents without comments from here: `/examples/arangodb/collections/menu`
|
||||
|
||||
```json
|
||||
{
|
||||
"urn": "/", // Link
|
||||
"name": {
|
||||
"en": "Main page",
|
||||
"ru": "Главная страница"
|
||||
},
|
||||
"style": { // The `style` attribute
|
||||
"order": 0
|
||||
},
|
||||
"class": "",
|
||||
"icon": { // Icon from `/themes/default/css/icons`
|
||||
"style": { // The `style` attribute
|
||||
"rotate": "-135deg"
|
||||
},
|
||||
"class": "arrow circle" // Classes of the icon
|
||||
},
|
||||
"image": { // Image at the background @deprecated?
|
||||
"storage": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Settings
|
||||
*Settings of chat-robot and Web App*<br><br>
|
||||
Make sure you have a **settings** collection (can be created automatically) and at least one document with the "status" parameter set to "active"<br>
|
||||
You can copy a clean settings document without comments from here: `/examples/arangodb/collections/settings.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "active", // Values: "active", "inactive" (string) Status of the settings document?
|
||||
"project": {
|
||||
"name": "PROJECT" // Name of the projext (string)
|
||||
},
|
||||
"language": "en", // Will be converted to an instance of enumeration `mirzaev\arming_bot\models\enumerations\language`
|
||||
"currency": "usd", // Will be converted to an instance of enumeration `mirzaev\arming_bot\models\enumerations\currency`
|
||||
"company": {
|
||||
"identifier": null, // Example: "000000000000000" (string|null) (if `null` it will not be displayed)
|
||||
"tax": null, // Example: "000000000000" (string|null) (if `null` it will not be displayed)
|
||||
"name": null, // Example: "COMPANY" (string|null) (if `null` it will not be displayed)
|
||||
"offer": false, // Display the data of a public offer in the footer? (bool) (does not affect the `/offer` page generation)
|
||||
"sim": null, // Examples: "+7 000 000-00-00", "70000000000" (string|null) (if `null` it will not be displayed)
|
||||
"mail": null // Example: "name@domain.zone" (string|null) (if `null` it will not be displayed)
|
||||
},
|
||||
"search": {
|
||||
"enabled": true, // Enable the search input field?
|
||||
"position": "fixed" // Values: "fixed", "relative"
|
||||
},
|
||||
"catalog": {
|
||||
"categories": {
|
||||
"display": "column", // Values: "row" (flex wrap), "column" (rows) Type of the catalog display
|
||||
"structure": "lists", // Values: "pages" (pages with categories and products), "lists" (all categories as tree lists on the main page)
|
||||
"buttons": {
|
||||
"height": "120px", // Examples: "80px", "120px", "180px" (string|null) Height of buttons
|
||||
"background": "#fafafa", // Examples: "#fafafa", "yellow" (string|null) Color of buttons background
|
||||
"separator": {
|
||||
"enabled": true, // Enable separators?
|
||||
"width": "60%" // Exaples: "100%", "80%", "60%" (string|null) Width of separators over images (relative to image width from the left)
|
||||
},
|
||||
"lists": {
|
||||
"height": "800px", // Examples: "500px", "100%" (string|null) Maximum height of lists (`max-height` for animations working)
|
||||
"background": null, // Examples: "#fafafa", "yellow" (string|null) Color of lists
|
||||
"separator": null, // Examples: "#fafafa", "yellow" (string|null) Color of separators between rows
|
||||
"separated": true, // Separate lists from its buttons?
|
||||
"blocks": true, // Blocks instead of plain text?
|
||||
"arrow": true // Add arrow at the right?
|
||||
},
|
||||
"texts": {
|
||||
"position": {
|
||||
"vertical": "center" // Values: "top", "center", "bottom" (string|null) Position of texts of ascendants categories
|
||||
},
|
||||
"width": "max(8rem, 20%)", // Examples: "60%", "5rem", "max(8rem, 20%)" (string|null) Width of the text section (left side of buttons)
|
||||
"background": false, // Enable wrapping element for texts?
|
||||
"title": {
|
||||
"color": "#020202" // Examples: "#fafafa", "yellow" (string|null) Color of titles
|
||||
},
|
||||
"description": {
|
||||
"color": "#121212" // Examples: "#fafafa", "yellow" (string|null) Color of descriptions
|
||||
}
|
||||
},
|
||||
"images": {
|
||||
"filter": "contrast(1.2)" // Example: "contrast(1.2)" (string|null) Filter for images
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"enabled": true // Enable the cart button?
|
||||
},
|
||||
"css": {
|
||||
"catalog-button-cart-background": "#40a7e3",
|
||||
"product-button-cart-background": "#40a7e3"
|
||||
"catalog-button-cart-added-background": "#90be36",
|
||||
"product-button-cart-added-background": "#90be36"
|
||||
},
|
||||
"account": {
|
||||
"enabled": false // Enable the account section? (works only when opened from telegram `inline-button`)
|
||||
},
|
||||
"menu": {
|
||||
"enabled": true, // Enable the main menu?
|
||||
"position": "fixed" // Values: "fixed" (fixed to the bottom as a solid line), "relative" (at the top as separated buttons) (stirng) Position of the main menu
|
||||
},
|
||||
"header": {
|
||||
"enabled": true, // Enable the header?
|
||||
"position": "fixed" // Values: "fixed" (fixed to the bottom), "relative" (at the top) (stirng) Position of the header
|
||||
},
|
||||
"acquirings": {
|
||||
"robokassa": {
|
||||
"enabled": true, // Enable the Robokassa acquiring?
|
||||
"mode": "test" // Values: "work", "test" (string) Mode of the Robokassa acquiring
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
"deliveries": {
|
||||
"site": [], // Values: "sim", "name", "destination", "address", "commentary"
|
||||
"chat": [
|
||||
"sim",
|
||||
"name",
|
||||
"destination",
|
||||
"address",
|
||||
"commentary"
|
||||
] // Values: "sim", "name", "destination", "address", "commentary"
|
||||
}
|
||||
},
|
||||
"deliveries": {
|
||||
"cdek": {
|
||||
"enabled": true, // Enable CDEK delivery?
|
||||
"label": "CDEK" // Name of the CDEK delivery
|
||||
}
|
||||
},
|
||||
"chats": [
|
||||
{
|
||||
"id": null, // Example: -1002599391893 (int) (negative number) The telegram chat identifier
|
||||
"orders": true // Send orders? (for moderators)
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Suspensions
|
||||
*System of suspensions of chat-robot and Web App*<br><br>
|
||||
Make sure you have a **suspension** collection (can be created automatically)<br>
|
||||
You can copy a clean suspension document without comments from here: `/examples/arangodb/collections/suspension.json`
|
||||
```json
|
||||
{
|
||||
"end": 1726068961, // Unixtime
|
||||
"targets": {
|
||||
"chat-robot": true, // Block chat-robot
|
||||
"web app": true // Block "Web App"
|
||||
},
|
||||
"access": {
|
||||
"tester": true, // Account with `"tester": true`
|
||||
"developer": true // Account with `"developer": true`
|
||||
},
|
||||
"description": {
|
||||
"ru": "Разрабатываю каталог, поиск и корзину",
|
||||
"en": "I am developing a catalog, search and cart"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 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))
|
||||
|
||||
|
||||
|
||||
# Telegram-robot for registering for tasks
|
||||
|
||||
Synchronizes accounts with the site, displays a list of published applications with a selection by date, and also register to tasks
|
|
@ -1,51 +1,28 @@
|
|||
{
|
||||
"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/spetsresurs-telegram-registry-requests",
|
||||
"type": "robot",
|
||||
"require": {
|
||||
"triagens/arangodb": "^3.8",
|
||||
"mirzaev/arangodb": "^1.0",
|
||||
"badfarm/zanzara": "^0.9.1",
|
||||
"nyholm/psr7": "^1.8"
|
||||
},
|
||||
"license": "WTFPL",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"mirzaev\\spetsresurs\\telegram\\registry\\requests\\": "mirzaev/spetsresurs/telegram/registry/requests/system/"
|
||||
}
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Arsen Mirzaev Tatyano-Muradovich",
|
||||
"email": "arsen@mirzaev.sexy"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "stable",
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"php-http/discovery": 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;
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
[Unit]
|
||||
Description=Telegram chat-robot: @domain_of_your_robot_here
|
||||
|
||||
Wants=network.target
|
||||
After=syslog.target network-online.target
|
||||
|
||||
[Service]
|
||||
ExecStart=sudo -u www-data /usr/bin/php /var/www/huesos/mirzaev/huesos/system/public/robot.php
|
||||
PIDFile=/var/run/php/huesos.pid
|
||||
RemainAfterExit=no
|
||||
RuntimeMaxSec=3600s
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -1,83 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\controllers;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\controllers\core;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\http\enumerations\status;
|
||||
|
||||
/**
|
||||
* Controller of account
|
||||
*
|
||||
* @package mirzaev\huesos\controllers
|
||||
*
|
||||
* @param array $errors Registry of errors
|
||||
*
|
||||
* @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
|
||||
{
|
||||
/**
|
||||
* Errors
|
||||
*
|
||||
* @var array $errors Registry of errors
|
||||
*/
|
||||
protected array $errors = [
|
||||
'session' => [],
|
||||
'account' => [],
|
||||
'buffer' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* Write
|
||||
*
|
||||
* Write to the buffer
|
||||
*
|
||||
* @param mixed ...$parameters Parameters for writing to the buffer
|
||||
*
|
||||
* @return null
|
||||
*
|
||||
* @todo переделать под trait buffer
|
||||
*/
|
||||
public function write(mixed ...$parameters): null
|
||||
{
|
||||
if (!empty($parameters) && isset($this->account)) {
|
||||
// Received parameters and initialized model with buffer trait
|
||||
|
||||
// Declaring the buffer of deserialized parameters
|
||||
$deserialized = [];
|
||||
|
||||
foreach ($parameters as $name => $value) {
|
||||
// Iterate over parameters
|
||||
|
||||
// Validation of the parameter value
|
||||
if (mb_strlen(serialize($value)) > 4096) continue;
|
||||
|
||||
// Declaring the buffer of deserialized parameter
|
||||
$parameter = null;
|
||||
|
||||
// Deserializing name to multidimensional array
|
||||
foreach (array_reverse(explode('_', (string) $name)) as $key)
|
||||
$parameter = [$key => $parameter ?? (is_string($value) && 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 document from ArangoDB
|
||||
if (!empty($deserialized)) $this->account->write($deserialized, $this->errors['buffer']);
|
||||
|
||||
// Writing status of response
|
||||
$this->response->status = status::created;
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,375 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\controllers\api\acquirings;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\controllers\core,
|
||||
mirzaev\huesos\models\order,
|
||||
mirzaev\huesos\models\cart,
|
||||
mirzaev\huesos\models\product,
|
||||
mirzaev\huesos\models\telegram,
|
||||
mirzaev\huesos\models\acquirings\robokassa as model,
|
||||
mirzaev\huesos\models\enumerations\currency,
|
||||
mirzaev\huesos\models\enumerations\language;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\http\enumerations\content,
|
||||
mirzaev\minimal\http\enumerations\status;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
mirzaev\arangodb\document;
|
||||
|
||||
// Framework for Telegram
|
||||
use Zanzara\Zanzara as zanzara,
|
||||
Zanzara\Context as context,
|
||||
Zanzara\Config as config,
|
||||
Zanzara\Telegram\Type\Message as message;
|
||||
|
||||
// Event manager for PHP
|
||||
use React\EventLoop\Loop as loop;
|
||||
|
||||
// Built-in libraries
|
||||
use DateTime as datetime,
|
||||
Exception as exception;
|
||||
|
||||
/**
|
||||
* Controller of robokassa
|
||||
*
|
||||
* @package mirzaev\huesos\controllers\api\acquirings
|
||||
*
|
||||
* @param array $errors Registry of errors
|
||||
*
|
||||
* @method null result()
|
||||
* @method null success()
|
||||
* @method null fail()
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class robokassa extends core
|
||||
{
|
||||
/**
|
||||
* Errors
|
||||
*
|
||||
* @var array $errors Registry of errors
|
||||
*/
|
||||
protected array $errors = [
|
||||
'session' => [],
|
||||
'account' => [],
|
||||
'order' => [],
|
||||
'robokassa' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* Result
|
||||
*
|
||||
* @param null
|
||||
*/
|
||||
public function result(
|
||||
?string $OutSum = null,
|
||||
?string $InvId = null,
|
||||
?string $SignatureValue = null,
|
||||
?string $PaymentMethod = null,
|
||||
?string $IncSum = null,
|
||||
?string $IncCurrLabel = null,
|
||||
?string $IsTest = null,
|
||||
?string $EMail = null,
|
||||
?string $Fee = null,
|
||||
string ...$other
|
||||
): null {
|
||||
// Searching for the order
|
||||
$order = order::_read(
|
||||
filter: 'd._key == @_key && d.paid != true',
|
||||
sort: 'd.created DESC, d._key DESC',
|
||||
amount: 1,
|
||||
parameters: ['_key' => $InvId],
|
||||
errors: $this->errors['robokassa']
|
||||
);
|
||||
|
||||
if ($order instanceof order) {
|
||||
// Initialized the order
|
||||
|
||||
if (model::result(identifier: (int) $InvId, cost: $OutSum, hash: $SignatureValue, test: (bool) $IsTest, errors: $this->errors['robokassa'])) {
|
||||
// Verified the payment
|
||||
|
||||
// Initializing the cart
|
||||
$cart = $order->cart();
|
||||
|
||||
if ($cart instanceof cart) {
|
||||
// Initialized the cart
|
||||
|
||||
if ((float) ($cart->cost() + ($cart->buffer['delivery']['cost'] ?? 0)) === (float) $OutSum) {
|
||||
// Full payment received
|
||||
|
||||
// Writing that the order being been paid
|
||||
$order->paid = true;
|
||||
/* $order->term = null; */
|
||||
|
||||
if (document::update($order->__document(), errors: $this->errors['order'])) {
|
||||
// Writed into ArangoDB
|
||||
|
||||
// Initializing identifier of the order
|
||||
$identifier = $order->__document()->getKey();
|
||||
|
||||
// Initializing the customer account
|
||||
$customer = $order->account();
|
||||
|
||||
$config = new config();
|
||||
$config->setParseMode(config::PARSE_MODE_MARKDOWN);
|
||||
$config->useReactFileSystem(true);
|
||||
/* $config->setLoop(loop::get()); */
|
||||
|
||||
$robot = new zanzara(TELEGRAM_KEY, $config);
|
||||
|
||||
// Sending the message
|
||||
$robot->getTelegram()->sendMessage("✅ *Заказ \#$identifier оплачен*", ['chat_id' => $customer->identifier])
|
||||
->then(function ($message) use ($robot, $cart, $order, $identifier, $customer) {
|
||||
// Sended the message
|
||||
|
||||
// Initializing the chats for sending registry
|
||||
$chats = [];
|
||||
|
||||
foreach ($this->settings->chats as $chat) {
|
||||
// Iteration over chats
|
||||
|
||||
if ($chat['orders']) {
|
||||
// Authorized to receive orders
|
||||
|
||||
// Writing into the chats for sending registry
|
||||
$chats[] = $chat['id'];
|
||||
}
|
||||
}
|
||||
|
||||
if (count($chats) > 0) {
|
||||
// Initialized chats
|
||||
|
||||
// Declaring the formatted list of products for message
|
||||
$list = '';
|
||||
|
||||
// Declaring total cost of products
|
||||
$cost = $cart->cost($list);
|
||||
|
||||
// Escaping the formatted list of products for the message
|
||||
$list = telegram::unmarkdown($list);
|
||||
|
||||
// Initializing currency symbol
|
||||
$symbol = ($account->currency ?? currency::rub)->symbol();
|
||||
|
||||
// Declaring delivery texts
|
||||
$delivery_cost = $delivery_days = $delivery_address = '';
|
||||
|
||||
if (!empty($cart->buffer['delivery'])) {
|
||||
// Initialized delibery data
|
||||
|
||||
// Initializing delivery cost for message
|
||||
$delivery_cost = telegram::unmarkdown((string) $cart->buffer['delivery']['cost']);
|
||||
|
||||
// Initializing delivery days for message
|
||||
$delivery_days = telegram::unmarkdown((string) $cart->buffer['delivery']['days']);
|
||||
|
||||
// Initializing delivery address for message
|
||||
$delivery_address = telegram::unmarkdown($cart->buffer['delivery']['location']['name'] . ', ' . $cart->buffer['delivery']['street']);
|
||||
}
|
||||
|
||||
// Initializing receiver domain for the message
|
||||
$domain = telegram::unmarkdown($customer->domain);
|
||||
|
||||
// Initializing receiver SIM for the message
|
||||
$sim = telegram::unmarkdown((string) $customer->receiver['sim']);
|
||||
|
||||
// Initializing receiver name for the message
|
||||
$name = telegram::unmarkdown($customer->receiver['name']);
|
||||
|
||||
// Initializing receiver address for the message
|
||||
$address = telegram::unmarkdown($customer->receiver['address']);
|
||||
|
||||
// Initializing the message cost part
|
||||
$part_cost = "*Стоимость:* $cost$symbol";
|
||||
if (!empty($delivery_cost)) $part_cost .= " \+ $delivery_cost$symbol";
|
||||
if (!empty($delivery_days)) $part_cost .= " \($delivery_days дней\)";
|
||||
|
||||
// Initializing the message delivery part
|
||||
$part_delivery = '';
|
||||
if (!empty($delivery_address)) $part_delivery .= "*Адрес доставки:* $delivery_address";
|
||||
if (!empty($part_delivery)) $part_delivery = "\n$part_delivery";
|
||||
|
||||
// Initializing the message receiver part
|
||||
$part_receiver = "*Получатель:* @$domain";
|
||||
if (!empty($sim)) $part_receiver .= " $sim";
|
||||
if (!empty($name)) $part_receiver .= "\n*$name*";
|
||||
if (!empty($address)) $part_receiver .= "\n\n*Адрес:* $address";
|
||||
|
||||
// Initializing the message commentary part
|
||||
$part_commentary = '';
|
||||
if (!empty($order->commentary)) $part_commentary .= "\n\n*Комментарий:* " . $order->commentary;
|
||||
|
||||
// Sending messages
|
||||
$robot->getTelegram()->sendBulkMessage(
|
||||
$chats,
|
||||
<<<TXT
|
||||
📦 *Заказ* \#$identifier
|
||||
|
||||
$list
|
||||
$part_cost$part_delivery
|
||||
|
||||
$part_receiver$part_commentary
|
||||
TXT,
|
||||
[
|
||||
'reply_markup' => [
|
||||
'inline_keyboard' => [
|
||||
[
|
||||
['text' => '✉️ Чат с покупателем', 'url' => 'https://t.me/' . $customer->domain]
|
||||
]
|
||||
],
|
||||
'disable_notification' => true
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->write("OK$identifier")
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
} else throw new exception('Failed to update the order');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Success
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function success(
|
||||
?string $OutSum = null,
|
||||
?string $InvId = null,
|
||||
?string $SignatureValue = null,
|
||||
?string $IsTest = null,
|
||||
?string $Culture = null,
|
||||
string ...$other
|
||||
): null {
|
||||
// Searching for the order
|
||||
$order = order::_read(
|
||||
filter: 'd._key == @_key',
|
||||
sort: 'd.created DESC, d._key DESC',
|
||||
amount: 1,
|
||||
parameters: ['_key' => $InvId],
|
||||
errors: $this->errors['robokassa']
|
||||
);
|
||||
|
||||
if ($order instanceof order) {
|
||||
// Initialized the order
|
||||
|
||||
if (model::success(identifier: (int) $InvId, cost: $OutSum, hash: $SignatureValue, test: (bool) $IsTest, errors: $this->errors['robokassa'])) {
|
||||
// Verified the payment
|
||||
|
||||
if ($IsTest == 1) {
|
||||
// Enabled the test mode
|
||||
|
||||
// Initializing a paragraph abount the test mode
|
||||
$this->view->test = $this->language === language::ru ? 'Тестовый режим' : 'Test mode';
|
||||
}
|
||||
|
||||
// Render page
|
||||
$page = $this->view->render('api/acquirings/robokassa/success.html', [
|
||||
'h2' => $this->language === language::ru ? 'Заказ #' . $order->getKey() . ' оплачен' : 'Order #' . $order->getKey() . ' paid',
|
||||
'identifier' => $order->getKey(),
|
||||
'closing' => [
|
||||
'title' => $this->language === language::ru ? 'Закрытие окна' : 'Window closing',
|
||||
'iterator' => 30
|
||||
]
|
||||
]);
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->write($page)
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
|
||||
// Deinitializing rendered page
|
||||
unset($page);
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fail
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function fail(
|
||||
?string $OutSum = null,
|
||||
?string $InvId = null,
|
||||
?string $IsTest = null,
|
||||
?string $Culture = null,
|
||||
string ...$other
|
||||
): null {
|
||||
// Searching for the order
|
||||
$order = order::_read(
|
||||
filter: 'd._key == @_key',
|
||||
sort: 'd.created DESC, d._key DESC',
|
||||
amount: 1,
|
||||
parameters: ['_key' => $InvId],
|
||||
errors: $this->errors['robokassa']
|
||||
);
|
||||
|
||||
if ($order instanceof order) {
|
||||
// Initialized the order
|
||||
|
||||
if ($IsTest == 1) {
|
||||
// Enabled the test mode
|
||||
|
||||
// Initializing a paragraph abount the test mode
|
||||
$this->view->test = $this->language === language::ru ? 'Тестовый режим' : 'Test mode';
|
||||
}
|
||||
|
||||
// Render page
|
||||
$page = $this->view->render('api/acquirings/robokassa/fail.html', [
|
||||
'h2' => $this->language === language::ru ? 'Заказ #' . $order->getKey() . ' не оплачен' : 'Order #' . $order->getKey() . ' not paid',
|
||||
'identifier' => $order->getKey(),
|
||||
'closing' => [
|
||||
'title' => $this->language === language::ru ? 'Закрытие окна' : 'Window closing',
|
||||
'iterator' => 30
|
||||
]
|
||||
]);
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->write($page)
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
|
||||
// Deinitializing rendered page
|
||||
unset($page);
|
||||
}
|
||||
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,618 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\controllers;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\controllers\core,
|
||||
mirzaev\huesos\models\cart as model,
|
||||
mirzaev\huesos\models\product,
|
||||
mirzaev\huesos\models\menu,
|
||||
mirzaev\huesos\models\telegram,
|
||||
mirzaev\huesos\models\enumerations\currency,
|
||||
mirzaev\huesos\models\enumerations\language;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\http\enumerations\content,
|
||||
mirzaev\minimal\http\enumerations\status;
|
||||
|
||||
// Framework for Telegram
|
||||
use Zanzara\Zanzara as zanzara,
|
||||
Zanzara\Context as context,
|
||||
Zanzara\Config as config,
|
||||
Zanzara\Telegram\Type\Message as message;
|
||||
|
||||
// Event manager for PHP
|
||||
use React\EventLoop\Loop as loop;
|
||||
|
||||
/**
|
||||
* Controller of cart
|
||||
*
|
||||
* @package mirzaev\huesos\controllers
|
||||
*
|
||||
* @param model|null $cart Instance of the cart
|
||||
* @param array $errors Registry of errors
|
||||
*
|
||||
* @method null index() HTML-document with shopping cart and delivery settings
|
||||
* @method null product(int|string|null $identifier, ?string $type, int|string|null $amount) Change status of the product in the cart
|
||||
* @method null summary() Information about products in the cart
|
||||
*
|
||||
* @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
|
||||
{
|
||||
/**
|
||||
* Cart
|
||||
*
|
||||
* @var model|null $cart Instance of the cart
|
||||
*/
|
||||
protected readonly ?model $cart;
|
||||
|
||||
/**
|
||||
* Errors
|
||||
*
|
||||
* @var array $errors Registry of errors
|
||||
*/
|
||||
protected array $errors = [
|
||||
'session' => [],
|
||||
'account' => [],
|
||||
'menu' => [],
|
||||
'cart' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* Index
|
||||
*
|
||||
* HTML-document with shopping cart and delivery settings
|
||||
*
|
||||
* @param null
|
||||
*/
|
||||
public function index(): null
|
||||
{
|
||||
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']
|
||||
);
|
||||
|
||||
// Universalizing
|
||||
if ($this->view->menu instanceof _document) $this->view->menu = [$this->view->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)
|
||||
];
|
||||
|
||||
if (!empty($this->view->cart['products'])) {
|
||||
// Initialized products
|
||||
|
||||
// Declaring buffer of formatted products
|
||||
$formatted = [];
|
||||
|
||||
foreach ($this->view->cart['products'] as $product) {
|
||||
// Iterating over products
|
||||
|
||||
// Formatting products
|
||||
for ($i = 0; $i < $product['amount']; ++$i) {
|
||||
$formatted[] = [
|
||||
'weight' => $product['document']['weight'] ?? 0,
|
||||
...$product['document']['dimensions']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Deinitializing unnecessary variables
|
||||
unset($product);
|
||||
|
||||
// Initializing formatted products
|
||||
$this->view->formatted = $formatted;
|
||||
}
|
||||
|
||||
if (array_search('address', $this->settings->input['deliveries']['site'], true) !== false) {
|
||||
// The deliveries input is enabled for the site
|
||||
|
||||
// Declaring the deliveries list
|
||||
$deliveries = [];
|
||||
|
||||
foreach ($this->settings->deliveries as $key => $value) {
|
||||
// Iterating over deliveries
|
||||
|
||||
if ($value['enabled']) {
|
||||
// The delivery is enabled
|
||||
|
||||
// Writing into the deliveries list
|
||||
$deliveries[$key] = ['label' => $value['label']];
|
||||
}
|
||||
}
|
||||
|
||||
// Writing the deliveries list into the templater variables
|
||||
$this->view->deliveries = $deliveries;
|
||||
}
|
||||
|
||||
if (str_contains($this->request->headers['accept'], content::json->value)) {
|
||||
// Request for JSON response
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->json([
|
||||
'main' => '',
|
||||
'errors' => $this->errors
|
||||
])
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
} else if (str_contains($this->request->headers['accept'], content::any->value)) {
|
||||
// Request for any response
|
||||
|
||||
// Render page
|
||||
$page = $this->view->render('cart/page.html', [
|
||||
'h2' => $this->language === language::ru ? 'Корзина' : 'Cart' // @see https://git.mirzaev.sexy/mirzaev/huesos/issues/1
|
||||
]);
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->write($page)
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
|
||||
// Deinitializing rendered page
|
||||
unset($page);
|
||||
}
|
||||
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Product
|
||||
*
|
||||
* Change status of the product in the cart
|
||||
*
|
||||
* @param int|string|null $identifier Product identifier
|
||||
* @param string|null $type Action type (toggle, write, delete, set)
|
||||
* @param int|string|null $amount Amount of actions with the product (for write, delete and differently used by set)
|
||||
*
|
||||
* @return null
|
||||
*
|
||||
* @todo
|
||||
* 1. Add a limit on adding products to the cart based on the number of products in stock
|
||||
*/
|
||||
public function product(
|
||||
int|string|null $identifier = null,
|
||||
?string $type = null,
|
||||
int|string|null $amount = null
|
||||
): null {
|
||||
if (str_contains($this->request->headers['accept'], content::json->value)) {
|
||||
// Request for JSON response
|
||||
|
||||
// Declaring buffer with amount of the product in the cart
|
||||
$cart = 0;
|
||||
|
||||
// Validating @todo add throwing errors
|
||||
if (isset($identifier) && preg_match('/[\d]+/', urldecode($identifier), $matches)) $identifier = (int) $matches[0];
|
||||
else unset($identifier);
|
||||
|
||||
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 buffer with amount of the product in the cart
|
||||
$cart = $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
|
||||
if (isset($type) && preg_match('/[\w]+/', urldecode($type), $matches)) $type = $matches[0];
|
||||
else unset($type);
|
||||
|
||||
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 ($cart > 0) {
|
||||
// The cart has the product
|
||||
|
||||
// Deleting the product from the cart
|
||||
$this->cart->delete(product: $product, amount: $cart, errors: $this->errors['cart']);
|
||||
|
||||
// Reinitializing the buffer with amount of the product in the cart
|
||||
$cart = 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
|
||||
$cart = 1;
|
||||
}
|
||||
} else {
|
||||
// Received not the "toggle" command
|
||||
|
||||
// Validating @todo add throwing errors
|
||||
if (isset($amount) && preg_match('/[\d]+/', urldecode($amount), $matches)) $amount = (int) $matches[0];
|
||||
else unset($amount);
|
||||
|
||||
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 ($cart + $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
|
||||
$cart += $amount;
|
||||
}
|
||||
} else if ($type === 'delete') {
|
||||
// Decrease amount of the product in the cart
|
||||
|
||||
if ($cart - $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
|
||||
$cart -= $amount;
|
||||
}
|
||||
} else if ($type === 'set') {
|
||||
// Set amount of the product in the cart
|
||||
|
||||
if ($amount > -1 && $amount < 101) {
|
||||
// Validated amount to setting
|
||||
|
||||
if ($amount > $cart) {
|
||||
// Requested amount more than actual amount of the product in the cart
|
||||
|
||||
// Writing the product to the cart
|
||||
$this->cart->write(product: $product, amount: $amount - $cart, 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: $cart - $amount, errors: $this->errors['cart']);
|
||||
}
|
||||
|
||||
// Reinitializing the buffer with amount of the product in the cart
|
||||
$cart = $amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->json([
|
||||
'amount' => $cart, // $cart does not store a real value, but is calculated without a repeated request to ArangoDB
|
||||
'errors' => $this->errors
|
||||
])
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
|
||||
// Deinitializing buffer with amount of the product in the cart
|
||||
unset($cart);
|
||||
}
|
||||
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary
|
||||
*
|
||||
* Information about products in the cart
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function summary(): null
|
||||
{
|
||||
if (str_contains($this->request->headers['accept'], content::json->value)) {
|
||||
// Request for JSON response
|
||||
|
||||
// Initializing the cart
|
||||
$this->cart ??= $this->account?->cart() ?? $this->session?->cart();
|
||||
|
||||
if ($this->cart instanceof model) {
|
||||
// Initialized the cart
|
||||
|
||||
// Initializing products in the cart
|
||||
$products = $this->cart->products(language: $this->language, currency: $this->currency);
|
||||
|
||||
if (!empty($products)) {
|
||||
// Initialized products
|
||||
|
||||
// Initializing summary data of the cart
|
||||
$summary = $this->cart?->summary(currency: $this->currency, errors: $this->errors['cart']);
|
||||
|
||||
// Declaring buffer of formatted products
|
||||
$formatted = [];
|
||||
|
||||
foreach ($products as $product) {
|
||||
// Iterating over products
|
||||
|
||||
// Formatting products
|
||||
for ($i = 0; $i < $product['amount']; ++$i) {
|
||||
$formatted[] = [
|
||||
'weight' => $product['document']['weight'] ?? 0,
|
||||
...$product['document']['dimensions']
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deinitializing unnecessary variables
|
||||
unset($product);
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->json([
|
||||
'cost' => $summary['cost'] ?? 0,
|
||||
'amount' => $summary['amount'] ?? 0,
|
||||
'products' => $formatted ?? [],
|
||||
'errors' => $this->errors
|
||||
])
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
|
||||
// Deinitializing summary data of the cart
|
||||
unset($summary);
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Share
|
||||
*
|
||||
* Share the cart
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function share(): null
|
||||
{
|
||||
if (str_contains($this->request->headers['accept'], content::json->value)) {
|
||||
// Request for JSON response
|
||||
|
||||
// Initializing the cart
|
||||
$this->cart ??= $this->account?->cart() ?? $this->session?->cart();
|
||||
|
||||
if ($this->cart instanceof model) {
|
||||
// Initialized the cart
|
||||
|
||||
if ($share = $this->cart->share ?? $this->cart->share()) {
|
||||
// The cart is available for sharing
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->json([
|
||||
'share' => $share,
|
||||
'errors' => $this->errors
|
||||
])
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
}
|
||||
|
||||
// Deinitializing unnecessary variables
|
||||
unset($hash);
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach
|
||||
*
|
||||
* Attach the cart
|
||||
*
|
||||
* @return null
|
||||
*
|
||||
* @todo кажется я сделал хуйню
|
||||
*/
|
||||
public function attach(?string $share = null): null
|
||||
{
|
||||
if (str_contains($this->request->headers['accept'], content::json->value)) {
|
||||
// Request for JSON response
|
||||
|
||||
if ($this->account) {
|
||||
// Initialized the account
|
||||
|
||||
$config = new config();
|
||||
$config->setParseMode(config::PARSE_MODE_MARKDOWN);
|
||||
$config->useReactFileSystem(true);
|
||||
/* $config->setLoop(loop::get()); */
|
||||
|
||||
$robot = new zanzara(TELEGRAM_KEY, $config);
|
||||
|
||||
// Initializing cart
|
||||
$cart = model::_read(
|
||||
filter: 'd.share == @share',
|
||||
sort: 'd.updated DESC, d.created DESC, d._key DESC',
|
||||
amount: 1,
|
||||
page: 1,
|
||||
parameters: ['share' => $share]
|
||||
);
|
||||
|
||||
// Deinitializing unnecessary variables
|
||||
unset($share);
|
||||
|
||||
if ($cart instanceof model) {
|
||||
// Initialized the cart
|
||||
|
||||
// Unsharing the cart
|
||||
$cart->unshare();
|
||||
|
||||
// Connecting the cart to the account
|
||||
$edge = $this->account->connect($cart);
|
||||
|
||||
if (!empty($edge)) {
|
||||
// Connected the cart to the account
|
||||
|
||||
// Initializing products in the cart
|
||||
$products = $cart->products(language: $this->account->language ?? language::ru, currency: $this->account->currency ?? currency::rub);
|
||||
|
||||
if (!empty($products)) {
|
||||
// Initialized products in the cart
|
||||
|
||||
// Declaring the formatted list of products for message
|
||||
$list = '';
|
||||
|
||||
// Declaring total cost of products
|
||||
$cost = $cart->cost($list);
|
||||
|
||||
// Escaping the formatted list of products for the message
|
||||
$list = telegram::unmarkdown($list);
|
||||
|
||||
// Deinitializing unnecessary variables
|
||||
unset($products, $product, $row);
|
||||
|
||||
// Initializing identifier of the cart
|
||||
$identifier = $cart->getKey();
|
||||
|
||||
// Initializing currency symbol
|
||||
$symbol = ($this->account->currency ?? currency::rub)->symbol();
|
||||
|
||||
// Declaring delivery texts
|
||||
$delivery_cost = $delivery_days = $delivery_address = '';
|
||||
|
||||
if (!empty($cart->buffer['delivery'])) {
|
||||
// Initialized delibery data
|
||||
|
||||
// Initializing delivery cost for message
|
||||
$delivery_cost = telegram::unmarkdown((string) $cart->buffer['delivery']['cost']);
|
||||
|
||||
// Initializing delivery days for message
|
||||
$delivery_days = telegram::unmarkdown((string) $cart->buffer['delivery']['days']);
|
||||
|
||||
// Initializing delivery address for message
|
||||
$delivery_address = telegram::unmarkdown($cart->buffer['delivery']['location']['name'] . ', ' . $cart->buffer['delivery']['street']);
|
||||
}
|
||||
|
||||
// Initializing the message cost part
|
||||
$part_cost = "*Стоимость:* $cost$symbol";
|
||||
if (!empty($delivery_cost)) $part_cost .= " \+ $delivery_cost$symbol";
|
||||
if (!empty($delivery_days)) $part_cost .= " \($delivery_days дней\)";
|
||||
|
||||
// Initializing the message delivery part
|
||||
$part_delivery = '';
|
||||
if (!empty($delivery_address)) $part_cost .= "*Адрес доставки:* $delivery_address";
|
||||
if (!empty($part_delivery)) $part_delivery = "\n$part_delivery";
|
||||
|
||||
$robot->getTelegram()->sendMessage(
|
||||
<<<TXT
|
||||
🛒 *Добавлена корзина* \#$identifier
|
||||
|
||||
$list
|
||||
$part_cost$part_delivery
|
||||
TXT,
|
||||
[
|
||||
'chat_id' => $this->account->identifier,
|
||||
'reply_markup' => [
|
||||
'inline_keyboard' => [
|
||||
[
|
||||
['text' => '🚚 Оформить доставку', 'callback_data' => 'cart_delivery'],
|
||||
],
|
||||
],
|
||||
'disable_notification' => true
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Deinitializing unnecessary variables
|
||||
unset($cart, $list);
|
||||
}
|
||||
}
|
||||
|
||||
// Deinitializing unnecessary variables
|
||||
unset($cart);
|
||||
|
||||
$robot->getLoop()->run();
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->json([
|
||||
'success' => true,
|
||||
'errors' => $this->errors
|
||||
])
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
}
|
||||
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,482 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\controllers;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\controllers\core,
|
||||
mirzaev\huesos\models\enumerations\language,
|
||||
mirzaev\huesos\models\catalog as model,
|
||||
mirzaev\huesos\models\entry,
|
||||
mirzaev\huesos\models\category,
|
||||
mirzaev\huesos\models\product,
|
||||
mirzaev\huesos\models\cart,
|
||||
mirzaev\huesos\models\menu;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\http\enumerations\content,
|
||||
mirzaev\minimal\http\enumerations\protocol,
|
||||
mirzaev\minimal\http\request;
|
||||
|
||||
// Library for ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
/**
|
||||
* Controller of catalog
|
||||
*
|
||||
* @package mirzaev\huesos\controllers
|
||||
*
|
||||
* @param cart|null $cart Instance of the cart
|
||||
* @param array $errors Registry of errors
|
||||
*
|
||||
* @method null index(?string $product, ?string $category, ?string $brand, ?string $sort, ?string $text) Catalog
|
||||
*
|
||||
* @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
|
||||
{
|
||||
/**
|
||||
* Cart
|
||||
*
|
||||
* @var model|null $cart Instance of the cart
|
||||
*/
|
||||
protected readonly ?cart $cart;
|
||||
|
||||
/**
|
||||
* Errors
|
||||
*
|
||||
* @var array $errors Registry of errors
|
||||
*/
|
||||
protected array $errors = [
|
||||
'session' => [],
|
||||
'account' => [],
|
||||
'menu' => [],
|
||||
'catalog' => [],
|
||||
'cart' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* Catalog
|
||||
*
|
||||
* Browsing the catalog and receiving product data at the same time
|
||||
*
|
||||
* Receiving product data is necessary so that you can simultaneously open the product card at the desired location when opening the page
|
||||
*
|
||||
* @param string|null $product Product identifier (&product=1)
|
||||
* @param string|null $category Category identifier (&category=1)
|
||||
* @param string|null $brand Brand name (&brand=mirzaev)
|
||||
* @param string|null $sort Sort typ (&sort=cost)
|
||||
* @param string|null $text Search text (&text=zaloopa)
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function index(
|
||||
?string $product = null,
|
||||
?string $category = null,
|
||||
?string $brand = null,
|
||||
?string $sort = null,
|
||||
?string $text = null
|
||||
): null {
|
||||
// Initializing the cart
|
||||
$this->cart ??= $this->account?->cart() ?? $this->session?->cart();
|
||||
|
||||
// Initializing summary data of the cart
|
||||
$summary = $this->cart?->summary(currency: $this->currency, errors: $this->errors['cart']);
|
||||
|
||||
// Initializing the cart data
|
||||
$this->view->cart = [
|
||||
'products' => $this->cart?->products(language: $this->language, currency: $this->currency),
|
||||
'summary' => $summary
|
||||
];
|
||||
|
||||
// Validating received product identifier
|
||||
if (isset($product) && preg_match('/[\d]+/', $product, $matches)) $product = (int) $matches[0];
|
||||
else unset($product);
|
||||
|
||||
if (isset($product)) {
|
||||
// Received and validated product identifier
|
||||
|
||||
// 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: '{_id: d._id, 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: {"200": d.images[*].storage["200"], "800": d.images[*].storage["800"]}}',
|
||||
language: $this->language,
|
||||
currency: $this->currency,
|
||||
parameters: ['identifier' => $product],
|
||||
errors: $this->errors['catalog']
|
||||
);
|
||||
|
||||
// This is only for generate product card @todo need to move that in backed templated
|
||||
if (!empty($this->view->product)) {
|
||||
// Initialized the product
|
||||
|
||||
// Writing data about being in the cart
|
||||
$this->view->product = [
|
||||
'cart' => [
|
||||
'amount' => $this->view->cart['products'][$this->view->product->getId()]['amount'] ?? 0,
|
||||
'text' => [
|
||||
'cart' => $this->language === language::ru ? 'Корзина' : 'Cart',
|
||||
'add' => $this->language === language::ru ? 'Добавить в корзину' : 'Add to the cart',
|
||||
'added' => $this->language === language::ru ? 'Добавлено в корзину' : 'Added to the cart'
|
||||
]
|
||||
]
|
||||
] + $this->view->product->getAll();
|
||||
}
|
||||
}
|
||||
|
||||
// Intializing buffer of query parameters
|
||||
$parameters = [];
|
||||
|
||||
// Initializing buffer of filters query (AQL)
|
||||
$_filters = 'd.deleted != true && d.hidden != true';
|
||||
|
||||
// Validating received brand name
|
||||
if (isset($brand) && preg_match('/[\w\s]+/u', urldecode($brand), $matches)) $brand = $matches[0];
|
||||
else unset($brand);
|
||||
|
||||
if (isset($brand)) {
|
||||
// Received and validated filter by brand
|
||||
|
||||
// Writing to the buffer of filters query (AQL)
|
||||
$_filters .= ' && d.brand.@language == @brand';
|
||||
|
||||
// Writing to the buffer of query parameters
|
||||
$parameters['brand'] = $brand;
|
||||
}
|
||||
|
||||
// Writing to the session buffer
|
||||
$this->core->request(new request('PATCH', '/session/write', protocol::http_3, parameters: ['catalog_filters_brand' => $brand ?? null]));
|
||||
|
||||
// Writing to the account buffer
|
||||
$this->core->request(new request('PATCH', '/account/write', protocol::http_3, parameters: ['catalog_filters_brand' => $brand ?? null]));
|
||||
|
||||
// Writing to the current implementator of the buffer
|
||||
$this->session->buffer = ['catalog' => ['filters' => ['brand' => $brand ?? null]]] + ($this->session->buffer ?? []);
|
||||
|
||||
// Initialize buffer of filters query (AQL)
|
||||
$_sort = 'd.position ASC, d.name ASC, d.created DESC';
|
||||
|
||||
// Validating received sort
|
||||
if (isset($sort) && preg_match('/[\w\s]+/u', $sort, $matches)) $sort = $matches[0];
|
||||
else unset($sort);
|
||||
|
||||
if (isset($sort)) {
|
||||
// Received and validated sort
|
||||
|
||||
// Write to the buffer of sort query (AQL)
|
||||
$_sort = "d.@sort DESC, $_sort";
|
||||
|
||||
// Write to the buffer of query parameters
|
||||
$parameters['sort'] = $sort;
|
||||
}
|
||||
|
||||
// Writing to the session buffer
|
||||
$this->core->request(new request('PATCH', '/session/write', protocol::http_3, parameters: ['catalog_sort' => $sort ?? null]));
|
||||
|
||||
// Writing to the account buffer
|
||||
$this->core->request(new request('PATCH', '/account/write', protocol::http_3, parameters: ['catalog_sort' => $sort ?? null]));
|
||||
|
||||
// Validating
|
||||
if (isset($category) && preg_match('/[\d]+/', $category, $matches)) $category = (int) $matches[0];
|
||||
else unset($category);
|
||||
|
||||
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];
|
||||
|
||||
// Searching for entries that are descendants of $category
|
||||
$search = model::search(
|
||||
document: $category,
|
||||
amount: 200,
|
||||
depth: 100,
|
||||
categories_merge: 'name: v.name.@language',
|
||||
/* products_merge: 'DISTINCT MERGE(d, {name: d.name.@language, description: d.description.@language, compatibility: d.compatibility.@language, brand: d.brand.@language, cost: d.cost.@currency})', */
|
||||
parameters: ['language' => $this->language->name],
|
||||
errors: $this->errors['catalog']
|
||||
);
|
||||
|
||||
if (isset($this->settings->catalog['categories']['structure']) && $this->settings->catalog['categories']['structure'] === 'pages') {
|
||||
// Pages
|
||||
|
||||
// Write to the buffer of global variables of view templater
|
||||
$this->view->categories = $search['categories'];
|
||||
}
|
||||
|
||||
// Write to the buffer of global variables of view templater
|
||||
$this->view->products = $search['products'];
|
||||
|
||||
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($category)) {
|
||||
// Not received identifier of the category
|
||||
|
||||
if (isset($this->settings->catalog['categories']['structure']) && $this->settings->catalog['categories']['structure'] === 'pages') {
|
||||
// Pages
|
||||
|
||||
// Searching for root ascendants categories
|
||||
$this->view->categories = entry::ascendants(
|
||||
descendant: new category,
|
||||
return: 'DISTINCT MERGE(d, { name: d.name.@language})',
|
||||
parameters: ['language' => $this->language->name],
|
||||
errors: $this->errors['catalog']
|
||||
) ?? null;
|
||||
} else if (isset($this->settings->catalog['categories']['structure']) && $this->settings->catalog['categories']['structure'] === 'lists') {
|
||||
// Lists
|
||||
|
||||
// Initializing the list of all categories
|
||||
$categories = [];
|
||||
|
||||
// Initializing the structure of the list of all categories
|
||||
$structure = [];
|
||||
|
||||
foreach (
|
||||
entry::ascendants(
|
||||
descendant: new category,
|
||||
return: 'DISTINCT MERGE(d, { name: d.name.@language})',
|
||||
parameters: ['language' => $this->language->name],
|
||||
errors: $this->errors['catalog']
|
||||
) ?? null as $document
|
||||
) {
|
||||
// Iterating over found root ascendants categories
|
||||
|
||||
// Generating the list (entering into recursion)
|
||||
static::list(
|
||||
document: $document,
|
||||
language: $this->language,
|
||||
categories: $categories,
|
||||
structure: $structure,
|
||||
errors: $this->errors['catalog']
|
||||
);
|
||||
}
|
||||
|
||||
// Writing the list of all categories into the templater variable
|
||||
$this->view->categories = $categories;
|
||||
|
||||
// Writing the structure of the list of all categories into the templater variable
|
||||
$this->view->structure = $structure;
|
||||
}
|
||||
}
|
||||
|
||||
// Validating @todo add throwing errors
|
||||
if (isset($text) && preg_match('/[\w\s]+/u', urldecode($text), $matches) && mb_strlen($matches[0]) > 2) $text = $matches[0];
|
||||
else unset($text);
|
||||
|
||||
// Writing to the session buffer
|
||||
$this->core->request(new request('PATCH', '/session/write', protocol::http_3, parameters: ['catalog_search_text' => $text ?? null]));
|
||||
|
||||
// Writing to the account buffer
|
||||
$this->core->request(new request('PATCH', '/account/write', protocol::http_3, parameters: ['catalog_search_text' => $text ?? 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']
|
||||
);
|
||||
|
||||
// Universalizing
|
||||
if ($this->view->menu instanceof _document) $this->view->menu = [$this->view->menu];
|
||||
}
|
||||
|
||||
if (str_contains($this->request->headers['accept'] ?? [], content::json->value)) {
|
||||
// Request for JSON response
|
||||
|
||||
// Initializing the response body buffer
|
||||
$body = [
|
||||
'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 body buffer
|
||||
$body['categories'] = $this->view->render('catalog/elements/categories.html');
|
||||
}
|
||||
|
||||
if (isset($this->view->product)) {
|
||||
// Initialized product
|
||||
|
||||
// Writing data of the product to the response body buffer @todo GENERATE THIS ON THE SERVER
|
||||
$body['product'] = $this->view->product;
|
||||
}
|
||||
|
||||
if (isset($this->view->products)) {
|
||||
// Initialized products
|
||||
|
||||
// Render HTML-code of products and write to the response body buffer
|
||||
$body['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 body buffer
|
||||
$body['filters'] = $this->view->render('catalog/elements/filters.html');
|
||||
}
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->json($body + ['errors' => $this->errors])
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
|
||||
// Deinitializing the response body buffer
|
||||
unset($body);
|
||||
} else if (str_contains($this->request->headers['accept'], content::any->value)) {
|
||||
// Request for any response
|
||||
|
||||
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 ?? []);
|
||||
}
|
||||
|
||||
// Render page
|
||||
$page = $this->view->render('catalog/page.html', [
|
||||
'h2' => $this->language === language::ru ? 'Каталог' : 'Catalog' // @see https://git.mirzaev.sexy/mirzaev/huesos/issues/1
|
||||
]);
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->write($page)
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
|
||||
// Deinitializing rendered page
|
||||
unset($page);
|
||||
}
|
||||
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* List
|
||||
*
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function list(_document $document, language $language, array &$categories = [], array &$structure = [], array &$errors = []): void
|
||||
{
|
||||
// Initializing the object
|
||||
$category = new category;
|
||||
|
||||
if (method_exists($category, '__document')) {
|
||||
// Object can implement a document from ArangoDB
|
||||
|
||||
// Writing the instance of product document from ArangoDB to the implement object
|
||||
$category->__document($document);
|
||||
|
||||
// Writing the category into the list of categories
|
||||
$categories[$category->getId()] = $category;
|
||||
|
||||
// Writing the category into the structure of the list categories
|
||||
$structure[$category->getId()] = [];
|
||||
|
||||
// Searching for entries that are descendants of $category
|
||||
$search = model::search(
|
||||
document: $category,
|
||||
amount: 200,
|
||||
categories_merge: 'name: v.name.@language',
|
||||
parameters: ['language' => $language->name],
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
if (count($search['categories']) > 0) {
|
||||
// Found descendants categories
|
||||
|
||||
foreach ($search['categories'] as $document) {
|
||||
// Iterating over descendants categories
|
||||
|
||||
// Entering into descendant level of the recursion for the list generation
|
||||
static::list(
|
||||
document: $document,
|
||||
language: $language,
|
||||
categories: $categories,
|
||||
structure: $structure[$category->getId()],
|
||||
errors: $errors
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,244 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\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;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\core as minimal,
|
||||
mirzaev\minimal\controller,
|
||||
mirzaev\minimal\http\response,
|
||||
mirzaev\minimal\http\enumerations\status;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @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 controller
|
||||
{
|
||||
/**
|
||||
* Settings
|
||||
*
|
||||
* @var settings $settings Instance of the settings
|
||||
*/
|
||||
protected readonly settings $settings;
|
||||
|
||||
/**
|
||||
* Session
|
||||
*
|
||||
* @var session|null $session Instance of the session
|
||||
*/
|
||||
protected readonly session $session;
|
||||
|
||||
/**
|
||||
* Account
|
||||
*
|
||||
* @var account|null $account Instance of the account
|
||||
*/
|
||||
protected readonly ?account $account;
|
||||
|
||||
/**
|
||||
* Cart
|
||||
*
|
||||
* @var cart|null $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
|
||||
*/
|
||||
protected array $errors = [
|
||||
'session' => [],
|
||||
'account' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param minimal $core Initialize a controller?
|
||||
* @param bool $initialize Initialize a controller?
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @todo
|
||||
* 1. settings account и session не имеют проверок на возврат null
|
||||
* 2. TRANSIT EVERYTHING TO MIDDLEWARES
|
||||
*/
|
||||
public function __construct(minimal $core, 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;
|
||||
|
||||
// For the extends system
|
||||
parent::__construct(core: $core);
|
||||
|
||||
if ($initialize) {
|
||||
// Initializing is requested
|
||||
|
||||
// Initializing of models core (connect to ArangoDB...)
|
||||
new models(true);
|
||||
|
||||
// Initializing of the date until which the session will be active
|
||||
$expires = strtotime('+1 week');
|
||||
|
||||
// Initializing of default value of hash of the session
|
||||
$_COOKIE["session"] ??= null;
|
||||
|
||||
// Initializing of a session
|
||||
$this->session = new session($_COOKIE["session"], $expires, $this->errors['session']);
|
||||
|
||||
// Handle a problems with initializing a session
|
||||
if (!empty($this->errors['session'])) exit(1);
|
||||
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
|
||||
setcookie(
|
||||
'session',
|
||||
$this->session->hash,
|
||||
[
|
||||
'expires' => $expires,
|
||||
'path' => '/',
|
||||
'secure' => true,
|
||||
'httponly' => true,
|
||||
'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);
|
||||
|
||||
// Initializing of the language
|
||||
$this->language = $this->account?->language ?? $this->session?->buffer['language'] ?? $this->settings?->language ?? language::en;
|
||||
|
||||
// Initializing of the currency
|
||||
$this->currency = $this->account?->currency ?? $this->session?->buffer['currency'] ?? $this->settings?->currency ?? currency::usd;
|
||||
|
||||
// Initializing of preprocessor of views
|
||||
$this->view = new templater(
|
||||
session: $this->session,
|
||||
account: $this->account,
|
||||
settings: $this->settings
|
||||
);
|
||||
|
||||
// @todo перенести в middleware
|
||||
|
||||
// Search for suspensions
|
||||
$suspension = suspension::search();
|
||||
|
||||
if ($suspension && $suspension->targets['web app']) {
|
||||
// Found a suspension
|
||||
|
||||
if ($this->account) {
|
||||
// Initialized account
|
||||
|
||||
foreach ($suspension->access as $type => $status) {
|
||||
// Перебор статусов доступа
|
||||
|
||||
if ($status && $this->account->{$type}) {
|
||||
// Authorized account
|
||||
|
||||
// Exit (success)
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
goto suspension;
|
||||
} else {
|
||||
// Not initialized account
|
||||
|
||||
// Send the suspension page and exit (success)
|
||||
suspension:
|
||||
|
||||
// Write title of the page to templater global variables
|
||||
$this->view->title = match ($this->language) {
|
||||
language::en => 'Suspended',
|
||||
language::ru => 'Приостановлено',
|
||||
default => 'Suspended'
|
||||
};
|
||||
|
||||
// Write description of the suspension to templater global variables
|
||||
$this->view->description = $suspension->description[$this->language] ?? array_values($suspension->description)[0];
|
||||
|
||||
// Write message of remaining time of the suspension to templater global variables
|
||||
$this->view->remain = [
|
||||
'title' => match ($this->language) {
|
||||
language::en => 'Time remaining: ',
|
||||
language::ru => 'Осталось времени: ',
|
||||
default => 'Time remaining: '
|
||||
},
|
||||
'value' => $suspension?->message()
|
||||
];
|
||||
|
||||
// Send the suspension page
|
||||
echo $this->view->render('suspension/page.html');
|
||||
|
||||
// Exit (success)
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,87 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\controllers;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\controllers\core,
|
||||
mirzaev\huesos\models\menu;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\http\enumerations\content;
|
||||
|
||||
/**
|
||||
* Controller of pages
|
||||
*
|
||||
* @package mirzaev\huesos\controllers
|
||||
* @param array $errors Registry of errors
|
||||
*
|
||||
* @method null offer() Public offer
|
||||
*
|
||||
* @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
|
||||
{
|
||||
/**
|
||||
* Errors
|
||||
*
|
||||
* @var array $errors Registry of errors
|
||||
*/
|
||||
protected array $errors = [
|
||||
'session' => [],
|
||||
'account' => [],
|
||||
'menu' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* Public offer
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function offer(): null
|
||||
{
|
||||
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']
|
||||
);
|
||||
|
||||
// Universalizing
|
||||
if ($this->view->menu instanceof _document) $this->view->menu = [$this->view->menu];
|
||||
}
|
||||
|
||||
if (str_contains($this->request->headers['accept'], content::any->value)) {
|
||||
// Request for any response
|
||||
|
||||
// Render page
|
||||
$page = $this->view->render('offer/page.html');
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->write($page)
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
|
||||
// Deinitializing rendered page
|
||||
unset($page);
|
||||
}
|
||||
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,480 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\controllers;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\controllers\core,
|
||||
mirzaev\huesos\models\cart as model,
|
||||
mirzaev\huesos\models\product,
|
||||
mirzaev\huesos\models\menu,
|
||||
mirzaev\huesos\models\enumerations\language;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\http\enumerations\content,
|
||||
mirzaev\minimal\http\enumerations\status;
|
||||
|
||||
/**
|
||||
* Controller of cart
|
||||
*
|
||||
* @package mirzaev\huesos\controllers
|
||||
*
|
||||
* @param model|null $cart Instance of the cart
|
||||
* @param array $errors Registry of errors
|
||||
*
|
||||
* @method null index() HTML-document with shopping cart and delivery settings
|
||||
* @method null product(int|string|null $identifier, ?string $type, int|string|null $amount) Change status of the product in the cart
|
||||
* @method null summary() Information about products in the cart
|
||||
*
|
||||
* @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
|
||||
{
|
||||
/**
|
||||
* Cart
|
||||
*
|
||||
* @var model|null $cart Instance of the cart
|
||||
*/
|
||||
protected readonly ?model $cart;
|
||||
|
||||
/**
|
||||
* Errors
|
||||
*
|
||||
* @var array $errors Registry of errors
|
||||
*/
|
||||
protected array $errors = [
|
||||
'session' => [],
|
||||
'account' => [],
|
||||
'menu' => [],
|
||||
'cart' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* Index
|
||||
*
|
||||
* HTML-document with shopping cart and delivery settings
|
||||
*
|
||||
* @param null
|
||||
*/
|
||||
public function index(): null
|
||||
{
|
||||
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']
|
||||
);
|
||||
|
||||
// Universalizing
|
||||
if ($this->view->menu instanceof _document) $this->view->menu = [$this->view->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 (str_contains($this->request->headers['accept'], content::json->value)) {
|
||||
// Request for JSON response
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->json([
|
||||
'main' => '',
|
||||
'errors' => $this->errors
|
||||
])
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
} else if (str_contains($this->request->headers['accept'], content::any->value)) {
|
||||
// Request for any response
|
||||
|
||||
// Render page
|
||||
$page = $this->view->render('cart/page.html', [
|
||||
'h2' => $this->language === language::ru ? 'Корзина' : 'Cart' // @see https://git.mirzaev.sexy/mirzaev/huesos/issues/1
|
||||
]);
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->write($page)
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
|
||||
// Deinitializing rendered page
|
||||
unset($page);
|
||||
}
|
||||
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Product
|
||||
*
|
||||
* Change status of the product in the cart
|
||||
*
|
||||
* @param int|string|null $identifier Product identifier
|
||||
* @param string|null $type Action type (toggle, write, delete, set)
|
||||
* @param int|string|null $amount Amount of actions with the product (for write, delete and differently used by set)
|
||||
*
|
||||
* @return null
|
||||
*
|
||||
* @todo
|
||||
* 1. Add a limit on adding products to the cart based on the number of products in stock
|
||||
*/
|
||||
public function product(
|
||||
int|string|null $identifier = null,
|
||||
?string $type = null,
|
||||
int|string|null $amount = null
|
||||
): null {
|
||||
if (str_contains($this->request->headers['accept'], content::json->value)) {
|
||||
// Request for JSON response
|
||||
|
||||
// Declaring buffer with amount of the product in the cart
|
||||
$cart = 0;
|
||||
|
||||
// Validating @todo add throwing errors
|
||||
if (isset($identifier) && preg_match('/[\d]+/', urldecode($identifier), $matches)) $identifier = (int) $matches[0];
|
||||
else unset($identifier);
|
||||
|
||||
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 buffer with amount of the product in the cart
|
||||
$cart = $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
|
||||
if (isset($type) && preg_match('/[\w]+/', urldecode($type), $matches)) $type = $matches[0];
|
||||
else unset($type);
|
||||
|
||||
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 ($cart > 0) {
|
||||
// The cart has the product
|
||||
|
||||
// Deleting the product from the cart
|
||||
$this->cart->delete(product: $product, amount: $cart, errors: $this->errors['cart']);
|
||||
|
||||
// Reinitializing the buffer with amount of the product in the cart
|
||||
$cart = 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
|
||||
$cart = 1;
|
||||
}
|
||||
} else {
|
||||
// Received not the "toggle" command
|
||||
|
||||
// Validating @todo add throwing errors
|
||||
if (isset($amount) && preg_match('/[\d]+/', urldecode($amount), $matches)) $amount = (int) $matches[0];
|
||||
else unset($amount);
|
||||
|
||||
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 ($cart + $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
|
||||
$cart += $amount;
|
||||
}
|
||||
} else if ($type === 'delete') {
|
||||
// Decrease amount of the product in the cart
|
||||
|
||||
if ($cart - $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
|
||||
$cart -= $amount;
|
||||
}
|
||||
} else if ($type === 'set') {
|
||||
// Set amount of the product in the cart
|
||||
|
||||
if ($amount > -1 && $amount < 101) {
|
||||
// Validated amount to setting
|
||||
|
||||
if ($amount > $cart) {
|
||||
// Requested amount more than actual amount of the product in the cart
|
||||
|
||||
// Writing the product to the cart
|
||||
$this->cart->write(product: $product, amount: $amount - $cart, 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: $cart - $amount, errors: $this->errors['cart']);
|
||||
}
|
||||
|
||||
// Reinitializing the buffer with amount of the product in the cart
|
||||
$cart = $amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->json([
|
||||
'amount' => $cart, // $cart does not store a real value, but is calculated without a repeated request to ArangoDB
|
||||
'errors' => $this->errors
|
||||
])
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
|
||||
// Deinitializing buffer with amount of the product in the cart
|
||||
unset($cart);
|
||||
}
|
||||
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary
|
||||
*
|
||||
* Information about products in the cart
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function summary(): null
|
||||
{
|
||||
if (str_contains($this->request->headers['accept'], content::json->value)) {
|
||||
// Request for JSON response
|
||||
|
||||
// Initializing the cart
|
||||
$this->cart ??= $this->account?->cart() ?? $this->session?->cart();
|
||||
|
||||
if ($this->cart instanceof model) {
|
||||
// Initialized the cart
|
||||
|
||||
// Initializing summary data of the cart
|
||||
$summary = $this->cart?->summary(currency: $this->currency, errors: $this->errors['cart']);
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->json([
|
||||
'cost' => $summary['cost'] ?? 0,
|
||||
'amount' => $summary['amount'] ?? 0,
|
||||
'errors' => $this->errors
|
||||
])
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
|
||||
// Deinitializing summary data of the cart
|
||||
unset($summary);
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Share
|
||||
*
|
||||
* Share the cart
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function share(): null
|
||||
{
|
||||
if (str_contains($this->request->headers['accept'], content::json->value)) {
|
||||
// Request for JSON response
|
||||
|
||||
// Initializing the cart
|
||||
$this->cart ??= $this->account?->cart() ?? $this->session?->cart();
|
||||
|
||||
if ($this->cart instanceof model) {
|
||||
// Initialized the cart
|
||||
|
||||
if ($share = $this->cart->share ?? $this->cart->share()) {
|
||||
// The cart is available for sharing
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->json([
|
||||
'share' => $share,
|
||||
'errors' => $this->errors
|
||||
])
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
}
|
||||
|
||||
// Deinitializing unnecessary variables
|
||||
unset($hash);
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pay
|
||||
*
|
||||
* Pay for the cart
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function pay(): null
|
||||
{
|
||||
if (str_contains($this->request->headers['accept'], content::json->value)) {
|
||||
// Request for JSON response
|
||||
|
||||
// Initializing the cart
|
||||
$this->cart ??= $this->account?->cart() ?? $this->session?->cart();
|
||||
|
||||
if ($this->cart instanceof model) {
|
||||
// Initialized the cart
|
||||
|
||||
if ($share = $this->cart->share ?? $this->cart->share()) {
|
||||
// The cart is available for sharing
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->json([
|
||||
'share' => $share,
|
||||
'errors' => $this->errors
|
||||
])
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
}
|
||||
|
||||
// Deinitializing unnecessary variables
|
||||
unset($hash);
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Robokassa
|
||||
*
|
||||
* HTML-document with robokassa iframe
|
||||
*
|
||||
* @param null
|
||||
*
|
||||
* @todo THIS MUST BE A PAYMENT OF ORDER IN THE FUTURE, NOT CART
|
||||
*/
|
||||
public function robokassa(): null
|
||||
{
|
||||
// Initializing the cart
|
||||
$this->cart ??= $this->account?->cart() ?? $this->session?->cart();
|
||||
|
||||
// Initializing the cart data
|
||||
$this->view->cart = $this->cart;
|
||||
$this->view->summary = $this->cart?->summary(currency: $this->currency);
|
||||
|
||||
if (str_contains($this->request->headers['accept'], content::any->value)) {
|
||||
// Request for any response
|
||||
|
||||
// Render page
|
||||
$page = $this->view->render('iframes/robokassa.html');
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->write($page)
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
|
||||
// Deinitializing rendered page
|
||||
unset($page);
|
||||
}
|
||||
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,238 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\controllers;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\controllers\core,
|
||||
mirzaev\huesos\models\account;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\http\enumerations\content,
|
||||
mirzaev\minimal\http\enumerations\status;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\document;
|
||||
|
||||
/**
|
||||
* Controller of session
|
||||
*
|
||||
* @package mirzaev\huesos\controllers
|
||||
*
|
||||
* @param array $errors Registry of errors
|
||||
*
|
||||
* @method null write(string ...$parameters) Write to the session buffer
|
||||
*
|
||||
* @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
|
||||
{
|
||||
/**
|
||||
* Errors
|
||||
*
|
||||
* @var array $errors Registry of errors
|
||||
*/
|
||||
protected array $errors = [
|
||||
'session' => [],
|
||||
'account' => [],
|
||||
'buffer' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* Connect session to the telegram account
|
||||
*
|
||||
* @see https://core.telegram.org/bots/webapps#initializing-mini-apps
|
||||
* @see https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app
|
||||
* @see https://core.telegram.org/bots/webapps#webappinitdata
|
||||
*
|
||||
* @param ?string $user JSON
|
||||
* @param ?string $chat_instance
|
||||
* @param ?string $chat_type
|
||||
* @param ?string $auth_date
|
||||
* @param ?string $hash
|
||||
* @param ?string $query_id
|
||||
* @param ?string $signature
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function telegram(
|
||||
?string $user = null,
|
||||
?string $chat_instance = null,
|
||||
?string $chat_type = null,
|
||||
?string $auth_date = null,
|
||||
?string $hash = null,
|
||||
?string $query_id = null,
|
||||
?string $signature = null
|
||||
): null {
|
||||
if (str_contains($this->request->headers['accept'], content::json->value)) {
|
||||
// Request for JSON response
|
||||
|
||||
// Declaring variables in the correct scope
|
||||
$identifier = $domain = $language = null;
|
||||
|
||||
// Initializing data of the account
|
||||
$data = json_decode($user);
|
||||
|
||||
// Initializing avatar of the account
|
||||
$avatar = $data->photo_url;
|
||||
|
||||
if ($connected = isset($this->account)) {
|
||||
// Found the account
|
||||
|
||||
// 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 (isset($user, $auth_date, $hash)) {
|
||||
// Received required parameters
|
||||
|
||||
$buffer = ['user' => $user];
|
||||
|
||||
if (isset($query_id)) $buffer += ['query_id' => $query_id];
|
||||
if (isset($signature)) $buffer += ['signature' => $signature];
|
||||
if (isset($chat_instance)) $buffer += ['chat_instance' => $chat_instance];
|
||||
if (isset($chat_type)) $buffer += ['chat_type' => $chat_type];
|
||||
|
||||
$buffer += ['auth_date' => $auth_date];
|
||||
|
||||
ksort($buffer);
|
||||
|
||||
$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 . 'telegram.php'), 'WebAppData', true);
|
||||
$_hash = bin2hex(hash_hmac('sha256', implode(PHP_EOL, $prepared), $key, true));
|
||||
|
||||
if (hash_equals($hash, $_hash)) {
|
||||
// Data confirmed (according to telegram documentation)
|
||||
|
||||
if (time() - $auth_date < 86400) {
|
||||
// Authorization date less than 1 day ago
|
||||
|
||||
// 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,
|
||||
'chat' => [
|
||||
'type' => $chat_type,
|
||||
'instance' => $chat_instance
|
||||
]
|
||||
],
|
||||
$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 avatar of the account
|
||||
$avatar = $data->photo_url;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->json([
|
||||
'connected' => (bool) $connected,
|
||||
'identifier' => $identifier ?? null,
|
||||
'domain' => $domain ?? null,
|
||||
'avatar' => $avatar ?? null,
|
||||
'language' => $language?->name ?? null,
|
||||
'errors' => $this->errors
|
||||
])
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
}
|
||||
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write
|
||||
*
|
||||
* Write to the buffer
|
||||
*
|
||||
* @param mixed ...$parameters Parameters for writing to the buffer
|
||||
*
|
||||
* @return null
|
||||
*
|
||||
* @todo переделать под trait buffer
|
||||
*/
|
||||
public function write(mixed ...$parameters): null
|
||||
{
|
||||
if (!empty($parameters) && isset($this->session)) {
|
||||
// Received parameters and initialized model with buffer trait
|
||||
|
||||
// Declaring the buffer of deserialized parameters
|
||||
$deserialized = [];
|
||||
|
||||
foreach ($parameters as $name => $value) {
|
||||
// Iterate over parameters
|
||||
|
||||
// Validation of the parameter value
|
||||
if (mb_strlen(serialize($value)) > 4096) continue;
|
||||
|
||||
// Declaring the buffer of deserialized parameter
|
||||
$parameter = null;
|
||||
|
||||
// Deserializing name to multidimensional array
|
||||
foreach (array_reverse(explode('_', (string) $name)) as $key)
|
||||
$parameter = [$key => $parameter ?? (is_string($value) && 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 document from ArangoDB
|
||||
if (!empty($deserialized)) $this->session->write($deserialized, $this->errors['buffer']);
|
||||
|
||||
// Writing status of response
|
||||
$this->response->status = status::created;
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,162 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\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;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
mirzaev\arangodb\document;
|
||||
|
||||
// Framework for Telegram
|
||||
use Zanzara\Telegram\Type\User as telegram;
|
||||
|
||||
// Library for ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Model of account
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class account extends core implements document_interface, collection_interface
|
||||
{
|
||||
use status, document_trait, buffer, cart {
|
||||
buffer::write as write;
|
||||
cart::initialize as cart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the collection in ArangoDB
|
||||
*/
|
||||
final public const string COLLECTION = 'account';
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*
|
||||
* @param int $identifier Identifier of the account
|
||||
* @param telegram|array|null $registration Данные для регистрация, если аккаунт не найден
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return static|null Объект аккаунта, если найден
|
||||
*/
|
||||
public static function initialize(int $identifier, telegram|array|null $registration = null, array &$errors = []): static|null
|
||||
{
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// Initialized the collection
|
||||
|
||||
// Initializing the account
|
||||
$result = collection::execute(
|
||||
<<<'AQL'
|
||||
FOR d IN @@collection
|
||||
FILTER d.identifier == @identifier
|
||||
RETURN d
|
||||
AQL,
|
||||
[
|
||||
'@collection' => static::COLLECTION,
|
||||
'identifier' => $identifier
|
||||
],
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
if ($result instanceof _document) {
|
||||
// Initialized the account
|
||||
|
||||
// Initializing the object
|
||||
$account = new static;
|
||||
|
||||
if (method_exists($account, '__document')) {
|
||||
// Object can implement a document from ArangoDB
|
||||
|
||||
// Implementinf parameters
|
||||
if (isset($result->language)) $result->language = language::{$result->language};
|
||||
if (isset($result->currency)) $result->currency = currency::{$result->currency};
|
||||
|
||||
// Writing the instance of account document from ArangoDB to the implement object
|
||||
$account->__document($result);
|
||||
|
||||
// Exit (success)
|
||||
return $account;
|
||||
} else throw new exception('Class ' . static::class . ' does not implement a document from ArangoDB');
|
||||
} else if ($registration) {
|
||||
// Not found the account and registration is requested
|
||||
|
||||
// Creating account
|
||||
$_id = document::write(
|
||||
static::COLLECTION,
|
||||
(is_array($registration)
|
||||
? $registration :
|
||||
[
|
||||
'identifier' => $registration->getId(),
|
||||
'name' => [
|
||||
'first' => $registration->getFirstName(),
|
||||
'last' => $registration->getLastName()
|
||||
],
|
||||
'domain' => $registration->getUsername(),
|
||||
'robot' => $registration->isBot(),
|
||||
'menus' => [
|
||||
'attachments' => $registration->getAddedToAttachmentMenu()
|
||||
],
|
||||
'messages' => true,
|
||||
'groups' => [
|
||||
'join' => $registration->getCanJoinGroups(),
|
||||
'messages' => $registration->getCanReadAllGroupMessages()
|
||||
],
|
||||
'premium' => $registration->isPremium(),
|
||||
'language' => language::{$registration->getLanguageCode() ?? 'en'}?->name ?? 'en',
|
||||
'queries' => [
|
||||
'inline' => $registration->getSupportsInlineQueries()
|
||||
]
|
||||
]) + [
|
||||
'banned' => false,
|
||||
'tester' => false,
|
||||
'developer' => false,
|
||||
'access' => [
|
||||
'settings' => false
|
||||
],
|
||||
'version' => ROBOT_VERSION,
|
||||
'active' => true
|
||||
],
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
if ($_id) {
|
||||
// Created account
|
||||
|
||||
// Initializing of the account (without registration request)
|
||||
return static::initialize($identifier, errors: $errors);
|
||||
} else throw new exception('Failed to register account');
|
||||
} else throw new exception('Failed to find account');
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,260 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\acquirings;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\models\core,
|
||||
mirzaev\huesos\models\settings,
|
||||
mirzaev\huesos\models\cart,
|
||||
mirzaev\huesos\models\order,
|
||||
mirzaev\huesos\models\enumerations\currency;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
mirzaev\arangodb\document;
|
||||
|
||||
// Library for ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// Built-in libraries
|
||||
use exception,
|
||||
DomainException as exception_domain,
|
||||
RuntimeException as exception_runtime;
|
||||
|
||||
/**
|
||||
* Model of robokassa
|
||||
*
|
||||
* @package mirzaev\huesos\models\acquirings
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class robokassa extends core
|
||||
{
|
||||
/**
|
||||
* Link
|
||||
*
|
||||
* Generate the link to pay for the order
|
||||
*
|
||||
* @param order $order The order
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return string|null The link to pay for the order, if generated
|
||||
*/
|
||||
public static function link(order $order, array &$errors = []): ?string
|
||||
{
|
||||
try {
|
||||
// Initializing the settings
|
||||
$settings = settings::active();
|
||||
|
||||
if ($settings instanceof settings) {
|
||||
// Initialized the settings
|
||||
|
||||
if ($settings->acquirings['robokassa']['enabled']) {
|
||||
// Enabled the robokassa acquiring
|
||||
|
||||
// Declaring the robokassa settings
|
||||
$robokassa = null;
|
||||
|
||||
// Initializing the test mode register
|
||||
$test = 0;
|
||||
|
||||
if ($settings->acquirings['robokassa']['mode'] === 'work') {
|
||||
// Work mode
|
||||
|
||||
// Initializing the robokassa settings
|
||||
$robokassa = require(SETTINGS . DIRECTORY_SEPARATOR . 'acquirings' . DIRECTORY_SEPARATOR . 'robokassa' . DIRECTORY_SEPARATOR . 'work.php');
|
||||
} else if ($settings->acquirings['robokassa']['mode'] === 'test') {
|
||||
// Test mode
|
||||
|
||||
// Reinitializing the test mode register
|
||||
$test = 1;
|
||||
|
||||
// Initializing the robokassa settings
|
||||
$robokassa = require(SETTINGS . DIRECTORY_SEPARATOR . 'acquirings' . DIRECTORY_SEPARATOR . 'robokassa' . DIRECTORY_SEPARATOR . 'test.php');
|
||||
} else {
|
||||
// Failed to initialized the mode
|
||||
|
||||
// Exit (fail)
|
||||
throw new exception_domain('Failed to initialize the robokassa mode');
|
||||
}
|
||||
|
||||
// Initializing the cart
|
||||
$cart = $order->cart();
|
||||
|
||||
if ($cart instanceof cart) {
|
||||
// Initialized the cart
|
||||
|
||||
// Initializing robokassa parameters
|
||||
$login = $robokassa['login'];
|
||||
$password = $robokassa['passwords'][0];
|
||||
|
||||
// Initializing the order parameters
|
||||
$identifier = $order->getKey();
|
||||
|
||||
// Initializing the invoice parameters
|
||||
$description = "Оплата заказа";
|
||||
$cost = $cart->cost() + ($cart->buffer['delivery']['cost'] ?? 0);
|
||||
|
||||
// Generatings the MD5 hash
|
||||
$md5 = md5("$login:$cost:$identifier:$password");
|
||||
/*
|
||||
// Writing the MD5 hash into the order implementator
|
||||
$order->acquirings = ['robokassa' => ['hash' => $md5] + ($order->acquirings['robokassa'] ?? [])] + ($order->acquirings ?? []);
|
||||
|
||||
if (document::update($order->__document(), errors: $errors)) {
|
||||
// Writed to ArangoDB */
|
||||
|
||||
// Exit (success)
|
||||
return "https://auth.robokassa.ru/Merchant/Index.aspx?MerchantLogin=$login&OutSum=$cost&InvId=$identifier&Description=$description&SignatureValue=$md5&IsTest=$test";
|
||||
/* } else throw new exception('Failed to write the robokassa hash into the order'); */
|
||||
} else {
|
||||
// Not initialized the cart
|
||||
|
||||
// Exit (fail)
|
||||
throw new exception_runtime('Failed to initialize the cart');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify (result)
|
||||
*
|
||||
* Generate hash by arguments and verify for result with the hash
|
||||
*
|
||||
* @param int $identifier Identifier of the order
|
||||
* @param string $cost Cost of the order (can be with two zeros, like 1000.00)
|
||||
* @param string $hash Hash of the order
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return string|null The link to pay for the order, if generated
|
||||
*/
|
||||
public static function result(int $identifier, string $cost, string $hash, bool $test = false, array &$errors = []): ?bool
|
||||
{
|
||||
try {
|
||||
// Initializing the settings
|
||||
$settings = settings::active();
|
||||
|
||||
if ($settings instanceof settings) {
|
||||
// Initialized the settings
|
||||
|
||||
if ($settings->acquirings['robokassa']['enabled']) {
|
||||
// Enabled the robokassa acquiring
|
||||
|
||||
// Declaring the robokassa settings
|
||||
$robokassa = null;
|
||||
|
||||
if ($test) {
|
||||
// Test mode
|
||||
|
||||
// Initializing the robokassa settings
|
||||
$robokassa = require(SETTINGS . DIRECTORY_SEPARATOR . 'acquirings' . DIRECTORY_SEPARATOR . 'robokassa' . DIRECTORY_SEPARATOR . 'test.php');
|
||||
} else {
|
||||
// Work mode
|
||||
|
||||
// Initializing the robokassa settings
|
||||
$robokassa = require(SETTINGS . DIRECTORY_SEPARATOR . 'acquirings' . DIRECTORY_SEPARATOR . 'robokassa' . DIRECTORY_SEPARATOR . 'work.php');
|
||||
}
|
||||
|
||||
// Initializing robokassa parameters
|
||||
$password = $robokassa['passwords'][1];
|
||||
|
||||
// Generatings the MD5 hash
|
||||
$md5 = strtoupper(md5("$cost:$identifier:$password"));
|
||||
|
||||
// Exit (success)
|
||||
return $md5 === $hash;
|
||||
}
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify (success)
|
||||
*
|
||||
* Generate hash by arguments and verify for success with the hash
|
||||
*
|
||||
* @param int $identifier Identifier of the order
|
||||
* @param string $cost Cost of the order (can be with two zeros, like 1000.00)
|
||||
* @param string $hash Hash of the order
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return string|null The link to pay for the order, if generated
|
||||
*/
|
||||
public static function success(int $identifier, string $cost, string $hash, bool $test = false, array &$errors = []): ?bool
|
||||
{
|
||||
try {
|
||||
// Initializing the settings
|
||||
$settings = settings::active();
|
||||
|
||||
if ($settings instanceof settings) {
|
||||
// Initialized the settings
|
||||
|
||||
if ($settings->acquirings['robokassa']['enabled']) {
|
||||
// Enabled the robokassa acquiring
|
||||
|
||||
// Declaring the robokassa settings
|
||||
$robokassa = null;
|
||||
|
||||
if ($test) {
|
||||
// Test mode
|
||||
|
||||
// Initializing the robokassa settings
|
||||
$robokassa = require(SETTINGS . DIRECTORY_SEPARATOR . 'acquirings' . DIRECTORY_SEPARATOR . 'robokassa' . DIRECTORY_SEPARATOR . 'test.php');
|
||||
} else {
|
||||
// Work mode
|
||||
|
||||
// Initializing the robokassa settings
|
||||
$robokassa = require(SETTINGS . DIRECTORY_SEPARATOR . 'acquirings' . DIRECTORY_SEPARATOR . 'robokassa' . DIRECTORY_SEPARATOR . 'work.php');
|
||||
}
|
||||
|
||||
// Initializing robokassa parameters
|
||||
$password = $robokassa['passwords'][0];
|
||||
|
||||
// Generatings the MD5 hash
|
||||
$md5 = md5("$cost:$identifier:$password");
|
||||
|
||||
// Exit (success)
|
||||
return $md5 === $hash;
|
||||
}
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,689 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\models\core,
|
||||
mirzaev\huesos\models\reservation,
|
||||
mirzaev\huesos\models\traits\buffer,
|
||||
mirzaev\huesos\models\traits\document as document_trait,
|
||||
mirzaev\huesos\models\interfaces\document as document_interface,
|
||||
mirzaev\huesos\models\interfaces\collection as collection_interface,
|
||||
mirzaev\huesos\models\enumerations\language,
|
||||
mirzaev\huesos\models\enumerations\currency;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
mirzaev\arangodb\document;
|
||||
|
||||
// Library for ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// Built-in libraries
|
||||
use DateTime as datetime,
|
||||
Exception as exception;
|
||||
|
||||
/**
|
||||
* Model of cart
|
||||
*
|
||||
* @uses reservation
|
||||
* @package mirzaev\huesos\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class cart extends core implements document_interface, collection_interface
|
||||
{
|
||||
use document_trait, buffer {
|
||||
buffer::write as buffer_write;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->name . ' collection: ' . product::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . reservation::TYPE->name . ' collection: ' . reservation::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'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->name . ' collection: ' . product::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . reservation::TYPE->name . ' collection: ' . reservation::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'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->name . ' collection: ' . product::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . reservation::TYPE->name . ' collection: ' . reservation::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write
|
||||
*
|
||||
* Write the product into 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->name . ' collection: ' . product::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . reservation::TYPE->name . ' collection: ' . reservation::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'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->name . ' collection: ' . product::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . reservation::TYPE->name . ' collection: ' . reservation::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Share
|
||||
*
|
||||
* Generate hash for sharing the cart
|
||||
*
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return string|false Hash for sharing, if generated and writed to ArangoDB
|
||||
*/
|
||||
public function share(array &$errors = []): string|false
|
||||
{
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// Initialized collection
|
||||
|
||||
// Generating hash and writing to the cart implement instance
|
||||
$this->share = sodium_bin2hex(sodium_crypto_generichash($this->getId()));
|
||||
|
||||
if (document::update($this->__document(), errors: $errors)) {
|
||||
// Writed to ArangoDB
|
||||
|
||||
// Exit (success)
|
||||
return $this->share;
|
||||
} else throw new exception('Failed to write confirmed cart to ArangoDB');
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unshare
|
||||
*
|
||||
* Deleting hash for sharing the cart
|
||||
*
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return bool Is the cart unshared?
|
||||
*/
|
||||
public function unshare(array &$errors = []): bool
|
||||
{
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// Initialized collection
|
||||
|
||||
// Deleting hash and writing to the cart implement instance
|
||||
$this->share = null;
|
||||
|
||||
if (document::update($this->__document(), errors: $errors)) {
|
||||
// Writed to ArangoDB
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
} else throw new exception('Failed to write confirmed cart to ArangoDB');
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Account
|
||||
*
|
||||
* Search for the connected account
|
||||
*
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return account|null The connected account, if found
|
||||
*/
|
||||
public function account(array &$errors = []): ?account
|
||||
{
|
||||
try {
|
||||
if ($result = collection::execute(
|
||||
<<<'AQL'
|
||||
FOR v IN 1..1 OUTBOUND @cart GRAPH users
|
||||
FILTER PARSE_IDENTIFIER(v._id).collection == @account
|
||||
LIMIT 1
|
||||
return v
|
||||
AQL,
|
||||
[
|
||||
'cart' => $this->document->getId(),
|
||||
'account' => account::COLLECTION
|
||||
],
|
||||
errors: $errors
|
||||
)) {
|
||||
// Found the instance of the account connected to the account
|
||||
|
||||
// Initializing the object
|
||||
$account = new account;
|
||||
|
||||
if (method_exists($account, '__document')) {
|
||||
// Object can implement a document from ArangoDB
|
||||
|
||||
// Implementinf parameters
|
||||
if (isset($result->language)) $result->language = language::{$result->language};
|
||||
if (isset($result->currency)) $result->currency = currency::{$result->currency};
|
||||
|
||||
// Writing the instance of the account document from ArangoDB to the implement object
|
||||
$account->__document($result);
|
||||
|
||||
// Exit (success)
|
||||
return $account;
|
||||
}
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cost
|
||||
*
|
||||
* Generate the cart products cost
|
||||
*
|
||||
* @param string &$list List of the calculated products
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return float|int|null The cart products cost, if generated
|
||||
*/
|
||||
public function cost(string &$list = '', array &$errors = []): float|int|null
|
||||
{
|
||||
try {
|
||||
// Initializing the account
|
||||
$account = $this->account($errors);
|
||||
|
||||
if ($account instanceof account) {
|
||||
// Initialized the account
|
||||
|
||||
// Initializing products in the cart
|
||||
$products = $this->products(language: $account->language ?? language::ru, currency: $account->currency ?? currency::rub);
|
||||
|
||||
if (!empty($products)) {
|
||||
// Initialized products in the cart
|
||||
|
||||
// Declaring total cost of products
|
||||
$cost = 0;
|
||||
|
||||
// Initializing iterator of rows
|
||||
$row = 0;
|
||||
|
||||
foreach ($products as $product) {
|
||||
// Iterating over products
|
||||
|
||||
// Generating formatted list of products for message
|
||||
$list .= ++$row . '. ' . $product['document']['name'] . ' (' . $product['amount'] . 'шт)' . "\n";
|
||||
|
||||
// Generating total cost of products
|
||||
$cost += $product['document']['cost'] * $product['amount'];
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
return $cost;
|
||||
}
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Order
|
||||
*
|
||||
* Create the order and connect to the cart and to the account
|
||||
* Make the cart ordered (the account will create a new cart)
|
||||
*
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return order|null The order, if created
|
||||
*
|
||||
* @todo Handling errors
|
||||
*/
|
||||
public function order(array &$errors = []): ?order
|
||||
{
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(order::COLLECTION, order::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(account::COLLECTION, account::TYPE, errors: $errors)) {
|
||||
// Initialized collections
|
||||
|
||||
// Searching for the order
|
||||
$existed = collection::execute(
|
||||
<<<'AQL'
|
||||
FOR v IN 1..1 OUTBOUND @cart GRAPH users
|
||||
FILTER PARSE_IDENTIFIER(v._id).collection == @order
|
||||
LIMIT 1
|
||||
return v
|
||||
AQL,
|
||||
[
|
||||
'cart' => $this->document->getId(),
|
||||
'order' => order::COLLECTION
|
||||
],
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
if ($existed) {
|
||||
// The order has already been created
|
||||
|
||||
// Initializing the object
|
||||
$order = new order;
|
||||
|
||||
if (method_exists($order, '__document')) {
|
||||
// Object can implement a document from ArangoDB
|
||||
|
||||
// Writing the instance of the order document from ArangoDB to the implement object
|
||||
$order->__document($existed);
|
||||
|
||||
// Exit (success)
|
||||
return $order;
|
||||
}
|
||||
} else {
|
||||
// The order has not been created
|
||||
|
||||
// Initializing a new order
|
||||
$_id = document::write(
|
||||
order::COLLECTION,
|
||||
[
|
||||
'active' => true,
|
||||
/* 'term' => (int) new datetime('+15 minutes')->ormat('U') */
|
||||
]
|
||||
);
|
||||
|
||||
if ($result = collection::execute(
|
||||
<<<'AQL'
|
||||
FOR d IN @@collection
|
||||
FILTER d._id == @_id && d.active == true
|
||||
RETURN d
|
||||
AQL,
|
||||
[
|
||||
'@collection' => order::COLLECTION,
|
||||
'_id' => $_id
|
||||
],
|
||||
errors: $errors
|
||||
)) {
|
||||
// Found the instance of just created the new order
|
||||
|
||||
// Writing the ordered status for the cart
|
||||
$this->document->ordered = true;
|
||||
|
||||
if (document::update($this->__document(), errors: $errors)) {
|
||||
// Writed into ArangoDB
|
||||
|
||||
// Initializing the object
|
||||
$order = new order;
|
||||
|
||||
if (method_exists($order, '__document')) {
|
||||
// Object can implement a document from ArangoDB
|
||||
|
||||
// Writing the instance of the order document from ArangoDB to the implement object
|
||||
$order->__document($result);
|
||||
|
||||
// Connecting the cart to the order
|
||||
$connected = $order->connect($this, $errors);
|
||||
|
||||
if ($connected) {
|
||||
// Connected the cart with the order
|
||||
|
||||
if ($result = collection::execute(
|
||||
<<<'AQL'
|
||||
FOR v IN 1..1 OUTBOUND @cart GRAPH users
|
||||
FILTER PARSE_IDENTIFIER(v._id).collection == @account
|
||||
LIMIT 1
|
||||
return v
|
||||
AQL,
|
||||
[
|
||||
'cart' => $this->document->getId(),
|
||||
'account' => account::COLLECTION
|
||||
],
|
||||
errors: $errors
|
||||
)) {
|
||||
// Found the instance of the account connected to the cart
|
||||
|
||||
// Initializing the object
|
||||
$account = new account;
|
||||
|
||||
if (method_exists($account, '__document')) {
|
||||
// Object can implement a document from ArangoDB
|
||||
|
||||
// Writing the instance of the account document from ArangoDB to the implement object
|
||||
$account->__document($result);
|
||||
|
||||
// Connecting the account with the order
|
||||
$connected = $account->connect($order, $errors);
|
||||
|
||||
if ($connected) {
|
||||
// Connected the account with the order
|
||||
|
||||
// Exit (success)
|
||||
return $order;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else throw new exception('Class ' . order::class . ' does not implement a document from ArangoDB');
|
||||
} else throw new exception('Failed to write the ordered status for the cart');
|
||||
} else throw new exception('Failed to create or find just created ' . static::class);
|
||||
}
|
||||
} else throw new exception('Failed to initialize ' . account::TYPE->name . ' collection: ' . account::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . reservation::TYPE->name . ' collection: ' . reservation::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . order::TYPE->name . ' collection: ' . order::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,743 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\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;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
mirzaev\arangodb\document;
|
||||
|
||||
// Framework for Excel
|
||||
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
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class catalog extends core
|
||||
{
|
||||
use yandex, files {
|
||||
yandex::download as 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];
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect parameter from all products
|
||||
*
|
||||
* @param string $documment Path to the EXCEL-document
|
||||
* @param int &$categories_loaded Counter of loaded categories
|
||||
* @param int &$categories_created Counter of created categories
|
||||
* @param int &$categories_updated Counter of updated categories
|
||||
* @param int &$categories_deleted Counter of deleted categories
|
||||
* @param int &$categories_new Counter of new categories
|
||||
* @param int &$categories_old Counter of old categories
|
||||
* @param int &$products_loaded Counter of loaded products
|
||||
* @param int &$products_created Counter of created products
|
||||
* @param int &$products_updated Counter of updated products
|
||||
* @param int &$products_deleted Counter of deleted products
|
||||
* @param int &$products_new Counter of new products
|
||||
* @param int &$products_old Counter of old products
|
||||
* @param language $language Language
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @todo
|
||||
* 1. Сначала создать все категории и затем снова по циклу пройтись уже создавать entry между ними
|
||||
* 2. Сжимать изображения
|
||||
*/
|
||||
public static function import(
|
||||
string $document,
|
||||
int &$categories_loaded = 0,
|
||||
int &$categories_created = 0,
|
||||
int &$categories_updated = 0,
|
||||
int &$categories_deleted = 0,
|
||||
int &$categories_old = 0,
|
||||
int &$categories_new = 0,
|
||||
int &$products_loaded = 0,
|
||||
int &$products_created = 0,
|
||||
int &$products_updated = 0,
|
||||
int &$products_deleted = 0,
|
||||
int &$products_old = 0,
|
||||
int &$products_new = 0,
|
||||
language $language = language::en,
|
||||
currency $currency = currency::usd,
|
||||
array &$errors = []
|
||||
): void {
|
||||
try {
|
||||
// Initializing the spreadsheet
|
||||
$spreadsheet = excel::open($document);
|
||||
|
||||
// Inititalizing worksheets
|
||||
$categories = $spreadsheet->getSheet('Категории');
|
||||
$products = $spreadsheet->getSheet('Товары');
|
||||
|
||||
// Counting old documents
|
||||
$categories_old = collection::count(category::COLLECTION, errors: $errors);
|
||||
$products_old = collection::count(product::COLLECTION, errors: $errors);
|
||||
|
||||
// Initializing the buffer of handler categories and products
|
||||
$handled = [
|
||||
'categories' => [],
|
||||
'products' => []
|
||||
];
|
||||
|
||||
foreach (
|
||||
$categories->nextRow(
|
||||
[
|
||||
'A' => 'identifier',
|
||||
'B' => 'name',
|
||||
'C' => 'category',
|
||||
'D' => 'images',
|
||||
'E' => 'position'
|
||||
],
|
||||
excel::KEYS_FIRST_ROW
|
||||
) as $number => $row
|
||||
) {
|
||||
// Iterate over categories
|
||||
|
||||
try {
|
||||
if (!empty($row['identifier']) && is_int($row['identifier']) && $row['identifier'] > 0 && !empty($row['name'])) {
|
||||
// Required cells are filled in
|
||||
|
||||
// Incrementing the counter of loaded categories
|
||||
++$categories_loaded;
|
||||
|
||||
// Declaring the variable with the status that a new category has been created
|
||||
$created = false;
|
||||
|
||||
// Declaring the variable with the category
|
||||
$category = null;
|
||||
|
||||
// Initializing the category
|
||||
$category = category::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $row['identifier']], errors: $errors);
|
||||
|
||||
if ($category instanceof category) {
|
||||
// Initialized the category
|
||||
|
||||
// Initializing name of the category
|
||||
if (empty($category->name) || empty($category->name[$language->name]) || $category->name[$language->name] !== $row['name'])
|
||||
$category->name = [$language->name => $row['name']] + ($category->name ?? []);
|
||||
|
||||
// Initializing position of the category
|
||||
if (empty($category->position) || $category->position === $row['position'])
|
||||
$category->position = $row['position'];
|
||||
} else {
|
||||
// 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
|
||||
);
|
||||
|
||||
// Initializing the category
|
||||
$category = category::_read(
|
||||
filter: 'd._id == @_id',
|
||||
parameters: ['_id' => $_id],
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
// Incrementing the counter of created categories
|
||||
if ($created) ++$categories_created;
|
||||
};
|
||||
|
||||
if ($category instanceof category) {
|
||||
// Initialized the category
|
||||
|
||||
if (!empty($row['category'])) {
|
||||
// Received the ascendant category
|
||||
|
||||
// Initializing the ascendant category
|
||||
$ascendant = category::_read(
|
||||
filter: 'd.identifier == @identifier',
|
||||
parameters: ['identifier' => (int) $row['category']],
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
if ($ascendant instanceof category) {
|
||||
// Found the ascendant category
|
||||
|
||||
// Deleting entries of the category in ArangoDB
|
||||
entry::banish($category, $errors);
|
||||
|
||||
// Writing the category as an entry to the ascendant category in ArangoDB
|
||||
entry::write($category, $ascendant, $errors);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($row['images'])) {
|
||||
// Received images
|
||||
|
||||
// Initializing new images of the category
|
||||
$images = static::folder(
|
||||
uri: explode(' ', mb_trim($row['images']))[0],
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
// Reinitialize images? (true, if no images found or their amount does not match)
|
||||
/* $reinitialize = !$category->images || count($category->images) !== count($images); */
|
||||
$reinitialize = true;
|
||||
|
||||
// Checking the identity of existing images with new images (if reinitialization of images has not yet been requested)
|
||||
/* if (!$reinitialize) foreach ($category->images as $key => $image) if ($reinitialize = $image['source'] !== $images[$key]) break; */
|
||||
|
||||
if ($reinitialize) {
|
||||
// Requested reinitialization of images
|
||||
|
||||
// Initializing the buffer of images
|
||||
$buffer = [];
|
||||
|
||||
foreach (is_array($images) ? $images : [] as $image) {
|
||||
// Iterating over new images
|
||||
|
||||
// Initializing identifier of the image
|
||||
$identifier = preg_replace('/\.\w+$/', '', $image->name);
|
||||
|
||||
// Initializing path to directory of images in storage
|
||||
$directory = DIRECTORY_SEPARATOR . 'categories' . DIRECTORY_SEPARATOR . $row['identifier'] . DIRECTORY_SEPARATOR . $identifier;
|
||||
|
||||
// Initializing URL of the image in storage
|
||||
$url = STORAGE . $directory;
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initializing images of the category
|
||||
$category->images = $buffer;
|
||||
}
|
||||
}
|
||||
|
||||
// Writing in ArangoDB
|
||||
$updated = document::update($category->__document(), errors: $errors);
|
||||
|
||||
// Incrementing the counter of updated categories
|
||||
if ($updated && !$created) ++$categories_updated;
|
||||
} else throw new exception('Failed to initialize category: ' . $row['name'] . " ($number)");
|
||||
}
|
||||
|
||||
// Writing to the registry of handled categories and products
|
||||
$handled['categories'][] = $row['identifier'];
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (
|
||||
$products->nextRow(
|
||||
[
|
||||
'A' => 'identifier',
|
||||
'B' => 'name',
|
||||
'C' => 'category',
|
||||
'D' => 'description',
|
||||
'E' => 'cost',
|
||||
'F' => 'weight',
|
||||
'G' => 'x',
|
||||
'H' => 'y',
|
||||
'I' => 'z',
|
||||
'J' => 'brand',
|
||||
'K' => 'compatibility',
|
||||
'L' => 'images',
|
||||
'M' => 'position'
|
||||
],
|
||||
excel::KEYS_FIRST_ROW
|
||||
) as $number => $row
|
||||
) {
|
||||
// Iterate over products
|
||||
|
||||
try {
|
||||
if (!empty($row['identifier']) && is_int($row['identifier']) && $row['identifier'] > 0 && !empty($row['name'])) {
|
||||
// Required cells are filled in
|
||||
|
||||
// Incrementing the counter of loaded products
|
||||
++$products_loaded;
|
||||
|
||||
// Declaring the variable with the status that a new product has been created
|
||||
$created = false;
|
||||
|
||||
// Declaring the variable with the product
|
||||
$product = null;
|
||||
|
||||
// Initializing the product
|
||||
$product = product::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $row['identifier']], errors: $errors);
|
||||
|
||||
if ($product instanceof product) {
|
||||
// Initialized the product
|
||||
|
||||
// Initializing name of the product
|
||||
if (empty($product->name) || empty($product->name[$language->name]) || $product->name[$language->name] !== $row['name'])
|
||||
$product->name = [$language->name => $row['name']] + ($product->name ?? []);
|
||||
|
||||
// Initializing description of the product
|
||||
if (empty($product->description) || empty($product->description[$language->name]) || $product->description[$language->name] !== $row['description'])
|
||||
$product->description = [$language->name => $row['description']] + ($product->description ?? []);
|
||||
|
||||
// Initializing brand of the product
|
||||
if (empty($product->brand) || empty($product->brand[$language->name]) || $product->brand[$language->name] !== $row['brand'])
|
||||
$product->brand = [$language->name => $row['brand']] + ($product->brand ?? []);
|
||||
|
||||
// Initializing compatibility of the product
|
||||
if (empty($product->compatibility) || empty($product->brand[$language->name]) || $product->compatibility[$language->name] !== $row['compatibility'])
|
||||
$product->compatibility = [$language->name => $row['compatibility']] + ($product->compatibility ?? []);
|
||||
|
||||
// Initializing position of the product
|
||||
if (empty($product->position) || $product->position !== $row['position'])
|
||||
$product->position = isset($row['position']) ? (int) $row['position'] : 0;
|
||||
} 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,
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
// Initializing the product
|
||||
$product = $created = product::_read('d._id == @_id', parameters: ['_id' => $_id], errors: $errors);
|
||||
|
||||
// Incrementing the counter of created products
|
||||
if ($created) ++$products_created;
|
||||
}
|
||||
|
||||
if ($product instanceof product) {
|
||||
// Initialized the product
|
||||
|
||||
if (!empty($row['category'])) {
|
||||
// Received the category
|
||||
|
||||
// Initializing the category
|
||||
$category = category::_read(
|
||||
filter: sprintf('d.identifier == %u', (int) $row['category']),
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
if ($category instanceof category) {
|
||||
// Found the ascendant category
|
||||
|
||||
// Deleting entries of the product in ArangoDB
|
||||
entry::banish($product, $errors);
|
||||
|
||||
// Writing the product as an entry to the ascendant category in ArangoDB
|
||||
entry::write($product, $category, $errors);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($row['images'])) {
|
||||
// Received images
|
||||
|
||||
// Initializing new images of the product
|
||||
$images = static::folder(
|
||||
uri: explode(' ', mb_trim($row['images']))[0],
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
// Reinitialize images? (true, if no images found or their amount does not match)
|
||||
/* $reinitialize = !$product->images || count($product->images) !== count($images); */
|
||||
$reinitialize = true;
|
||||
|
||||
// Checking the identity of existing images with new images (if reinitialization of images has not yet been requested)
|
||||
/* if (!$reinitialize) foreach ($product->images as $key => $image) if ($reinitialize = $image['source'] !== $images[$key]) break; */
|
||||
|
||||
if ($reinitialize) {
|
||||
// Requested reinitialization of images
|
||||
|
||||
// Initializing the buffer of images
|
||||
$buffer = [];
|
||||
|
||||
foreach (is_array($images) ? $images : [] as $image) {
|
||||
// Iterating over new images
|
||||
|
||||
// Initializing identifier of the image
|
||||
$identifier = preg_replace('/\.\w+$/', '', $image->name);
|
||||
|
||||
// Initializing path to directory of images in storage
|
||||
$directory = DIRECTORY_SEPARATOR . 'products' . DIRECTORY_SEPARATOR . $row['identifier'] . DIRECTORY_SEPARATOR . $identifier;
|
||||
|
||||
// Initializing URL of the image in storage
|
||||
$url = STORAGE . $directory;
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initializing images of the product
|
||||
$product->images = $buffer;
|
||||
}
|
||||
}
|
||||
|
||||
// Writing in ArangoDB
|
||||
$updated = document::update($product->__document(), errors: $errors);
|
||||
|
||||
// Incrementing the counter of updated products
|
||||
if ($updated && !$created) ++$products_updated;
|
||||
} else throw new exception('Failed to initialize product: ' . $row['name'] . " ($number)");
|
||||
}
|
||||
|
||||
// Writing to the registry of handled categories and products
|
||||
$handled['products'][] = $row['identifier'];
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Deleting old categories
|
||||
foreach (
|
||||
category::_read(
|
||||
/* filter: sprintf('%s - d.updated > 3600', time()), */
|
||||
sort: 'd.updated DESC',
|
||||
amount: 100000,
|
||||
errors: $errors
|
||||
) ?? [] as $document
|
||||
) {
|
||||
// Iterating over categories
|
||||
|
||||
// Initializing the category
|
||||
$category = new category(document: $document);
|
||||
|
||||
if (
|
||||
$category instanceof category
|
||||
&& array_search($category->identifier, $handled['categories']) === false
|
||||
) {
|
||||
// Not found identifier of the category 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
|
||||
);
|
||||
|
||||
// Deleting entries of the category in ArangoDB
|
||||
entry::banish(
|
||||
document: $category,
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
// Deleting the category in ArangoDB
|
||||
document::delete(
|
||||
document: $category->__document(),
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
// Incrementing the counter of deleted categories
|
||||
++$categories_deleted;
|
||||
}
|
||||
}
|
||||
|
||||
// Deleting old products
|
||||
foreach (
|
||||
product::_read(
|
||||
/* filter: sprintf('%s - d.updated > 3600', time()), */
|
||||
sort: 'd.updated DESC',
|
||||
amount: 100000,
|
||||
errors: $errors
|
||||
) ?? [] as $document
|
||||
) {
|
||||
// Iterating over products
|
||||
|
||||
// Initializing the category
|
||||
$product = new product(document: $document);
|
||||
|
||||
if (
|
||||
$product instanceof product
|
||||
&& array_search($product->identifier, $handled['products']) === false
|
||||
) {
|
||||
// Not found identifier of the product in the buffer of handled categories and products
|
||||
|
||||
// Deleting images of the product from storage
|
||||
static::delete(
|
||||
directory: STORAGE . DIRECTORY_SEPARATOR . 'products' . DIRECTORY_SEPARATOR . $product->identifier,
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
// Deleting entries of the product in ArangoDB
|
||||
entry::banish(
|
||||
document: $product,
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
// Deleting the product in ArangoDB
|
||||
document::delete(
|
||||
document: $product->__document(),
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
// Incrementing the counter of deleted products
|
||||
++$products_deleted;
|
||||
}
|
||||
}
|
||||
|
||||
// Counting new documents
|
||||
$categories_new = collection::count(collection: category::COLLECTION, errors: $errors);
|
||||
$products_new = collection::count(collection: product::COLLECTION, errors: $errors);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\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;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
mirzaev\arangodb\document;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
||||
|
||||
/**
|
||||
* Model of category
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class category extends core implements document_interface, collection_interface
|
||||
{
|
||||
use document_trait;
|
||||
|
||||
/**
|
||||
* Name of the collection in ArangoDB
|
||||
*/
|
||||
final public const string COLLECTION = 'category';
|
||||
|
||||
/**
|
||||
* Write the category
|
||||
*
|
||||
* @param int $identifier Identifier (unique)
|
||||
* @param array $name Name [['en' => value], ['ru' => значение]]
|
||||
* @param int|null $position Position for soring in the catalog (ASC)
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return string|null Identifier (_id) of the document in ArangoDB, if created
|
||||
*
|
||||
* @todo
|
||||
* 1. Bind parameters
|
||||
*/
|
||||
public static function write(
|
||||
int $identifier,
|
||||
array $name = [['en' => 'ERROR']],
|
||||
?int $position = null,
|
||||
array &$errors = []
|
||||
): string|null {
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// Initialized the collection
|
||||
|
||||
// Writing to ArangoDB and exit (success)
|
||||
return document::write(
|
||||
static::COLLECTION,
|
||||
[
|
||||
'identifier' => $identifier,
|
||||
'name' => $name,
|
||||
'position' => $position,
|
||||
'version' => ROBOT_VERSION
|
||||
],
|
||||
errors: $errors
|
||||
);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\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;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\enumerations\collection\type;
|
||||
|
||||
/**
|
||||
* Model of connect
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class connect extends core implements document_interface, collection_interface
|
||||
{
|
||||
use document_trait;
|
||||
|
||||
/**
|
||||
* Name of the collection in ArangoDB
|
||||
*/
|
||||
final public const string COLLECTION = 'connect';
|
||||
|
||||
/**
|
||||
* Type of the collection in ArangoDB
|
||||
*/
|
||||
public const type TYPE = type::edge;
|
||||
}
|
|
@ -1,218 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\model;
|
||||
|
||||
// Library for ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\connection as arangodb,
|
||||
mirzaev\arangodb\collection;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Core of models
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
class core extends model
|
||||
{
|
||||
/**
|
||||
* Path to the file with settings of connecting to the ArangoDB
|
||||
*/
|
||||
final public const string ARANGODB = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'settings' . DIRECTORY_SEPARATOR . 'arangodb.php';
|
||||
|
||||
/**
|
||||
* Instance of the session of ArangoDB
|
||||
*
|
||||
* @todo ПЕРЕДЕЛАТЬ В php 8.4
|
||||
*/
|
||||
protected static arangodb $arangodb;
|
||||
|
||||
/**
|
||||
* Constructor of an instance
|
||||
*
|
||||
* @param bool $initialize Initialize ...?
|
||||
* @param ?arangodb $arangodb Instance of a session of ArangoDB
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(bool $initialize = false, ?arangodb $arangodb = null)
|
||||
{
|
||||
// For the extends system
|
||||
parent::__construct($initialize);
|
||||
|
||||
if ($initialize) {
|
||||
// Initializing is requested
|
||||
|
||||
// Writing an instance of a session of ArangoDB to the property
|
||||
self::$arangodb = $arangodb ?? new arangodb(require static::ARANGODB);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read document from ArangoDB
|
||||
*
|
||||
* @param string $filter Expression for filtering (AQL)
|
||||
* @param string $sort Expression for sorting (AQL)
|
||||
* @param int $amount Amount of documents for collect
|
||||
* @param int $page Page
|
||||
* @param string $return Expression describing the parameters to return (AQL)
|
||||
* @param array $parameters Binded parameters for placeholders ['placeholder' => parameter]
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return mixed An array of instances of documents from ArangoDB, if they are found
|
||||
*/
|
||||
public static function _read(
|
||||
string $filter = '',
|
||||
string $sort = 'd.created DESC, d._key DESC',
|
||||
int $amount = 1,
|
||||
int $page = 1,
|
||||
string $return = 'd',
|
||||
array $parameters = [],
|
||||
array &$errors = []
|
||||
): _document|static|array|null {
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE)) {
|
||||
// Initialized the collection
|
||||
|
||||
// Read from ArangoDB
|
||||
$result = collection::execute(
|
||||
sprintf(
|
||||
<<<'AQL'
|
||||
FOR d IN @@collection
|
||||
%s
|
||||
%s
|
||||
LIMIT @offset, @amount
|
||||
RETURN %s
|
||||
AQL,
|
||||
empty($filter) ? '' : "FILTER $filter",
|
||||
empty($sort) ? '' : "SORT $sort",
|
||||
empty($return) ? 'd' : $return
|
||||
),
|
||||
[
|
||||
'@collection' => static::COLLECTION,
|
||||
'offset' => --$page <= 0 ? 0 : $page * $amount,
|
||||
'amount' => $amount
|
||||
] + $parameters,
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
if ($amount === 1 && $result instanceof _document) {
|
||||
// Received only 1 document and @todo rebuild
|
||||
|
||||
// Initializing the object
|
||||
$object = new static;
|
||||
|
||||
if (method_exists($object, '__document')) {
|
||||
// Object can implement a document from ArangoDB
|
||||
|
||||
// Writing the instance of document from ArangoDB to the implement object
|
||||
$object->__document($result);
|
||||
|
||||
// Exit (success)
|
||||
return $object;
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
return $result;
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write
|
||||
*
|
||||
* @param string $name Name of the property
|
||||
* @param mixed $value Value of the property
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __set(string $name, mixed $value = null): void
|
||||
{
|
||||
match ($name) {
|
||||
'arangodb' => (function () use ($value) {
|
||||
if (isset(static::$arangodb)) throw new exception('Forbidden to reinitialize the session of ArangoDB ($this::$arangodb)', 500);
|
||||
else if ($value instanceof arangodb) self::$arangodb = $value;
|
||||
else throw new exception('Session of connection to ArangoDB ($this::$arangodb) is need to be mirzaev\arangodb\connection', 500);
|
||||
})(),
|
||||
default => parent::__set($name, $value)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Read
|
||||
*
|
||||
* @param string $name Name of the property
|
||||
*
|
||||
* @return mixed Content of the property, if they are found
|
||||
*/
|
||||
public function __get(string $name): mixed
|
||||
{
|
||||
return match ($name) {
|
||||
default => parent::__get($name)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete
|
||||
*
|
||||
* @param string $name Name of the property
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __unset(string $name): void
|
||||
{
|
||||
// Deleting a property and exit (success)
|
||||
parent::__unset($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check of initialization
|
||||
*
|
||||
* @param string $name Name of the property
|
||||
*
|
||||
* @return bool The property is initialized?
|
||||
*/
|
||||
public function __isset(string $name): bool
|
||||
{
|
||||
// Check of initialization of the property and exit (success)
|
||||
return parent::__isset($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a static property or method
|
||||
*
|
||||
* @param string $name Name of the property or the method
|
||||
* @param array $arguments Arguments for the method
|
||||
*/
|
||||
public static function __callStatic(string $name, array $arguments): mixed
|
||||
{
|
||||
return match ($name) {
|
||||
'arangodb' => (new static)->__get('arangodb'),
|
||||
default => throw new exception("Not found: $name", 500)
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,197 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\deliveries;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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,
|
||||
CdekSDK2\Dto\Tariff as _tariff,
|
||||
CdekSDK2\BaseTypes\Tariff as tariff,
|
||||
CdekSDK2\BaseTypes\Tarifflist as tariffs,
|
||||
CdekSDK2\Constraints\Currencies as currencies,
|
||||
CdekSDK2\BaseTypes\Location as location,
|
||||
CdekSDK2\BaseTypes\Package as package;
|
||||
|
||||
// Built-in libraries
|
||||
use exception,
|
||||
DateTime as datetime;
|
||||
|
||||
/**
|
||||
* Model of CDEK
|
||||
*
|
||||
* @package mirzaev\huesos\models\deliveries
|
||||
*
|
||||
* @method cities|null location(string $name, array &$errors) Search for CDEK location by name
|
||||
*
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* Location
|
||||
*
|
||||
* Search for CDEK location by name (different villages, towns and farms may have the same names)
|
||||
*
|
||||
* @see https://api-docs.cdek.ru/182405028.html
|
||||
* @see https://github.com/TTATPuOT/cdek-sdk2.0/blob/master/docs/index.md#получение-cписка-городов
|
||||
*
|
||||
* @param string $name Location name
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return _cities|null Locations, if found
|
||||
*/
|
||||
public static function location(string $name, array &$errors = []): ?_cities
|
||||
{
|
||||
try {
|
||||
if (!empty(CDEK)) {
|
||||
// Initialized CDEK account data
|
||||
|
||||
// Initializing HTTP-client
|
||||
$client = new client(new guzzle, CDEK['account'], CDEK['secret']);
|
||||
|
||||
// Request
|
||||
$response = $client->cities()->getFiltered(['country_codes' => 'RU', 'city' => $name]);
|
||||
|
||||
if ($response->isOk()) {
|
||||
// Received response
|
||||
|
||||
// Exit (success)
|
||||
return $client->formatResponseList($response, _cities::class);
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate
|
||||
*
|
||||
* Calculate delivery from warehouse to recipient according to tariff
|
||||
*
|
||||
* @see https://api-docs.cdek.ru/63345430.html
|
||||
* @see https://github.com/TTATPuOT/cdek-sdk2.0/blob/master/docs/index.md#калькулятор-расчет-по-коду-тарифа
|
||||
*
|
||||
* @param int $from_location Location code (sender)
|
||||
* @param string $from_street Street (sender)
|
||||
* @param int $to_location Location code (receiver)
|
||||
* @param string $to_street Street (receiver)
|
||||
* @param string $tariff Tariff (identifier)
|
||||
* @param array $products Products that will be sent [weight, x, y, z]
|
||||
* @param datetime $date Date of sending
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return _tariff|null Calculating data, if calculated
|
||||
*/
|
||||
public static function calculate(
|
||||
int $from_location,
|
||||
string $from_street,
|
||||
int $to_location,
|
||||
string $to_street,
|
||||
int $tariff,
|
||||
array $products,
|
||||
datetime $date,
|
||||
array &$errors = []
|
||||
): ?_tariff {
|
||||
try {
|
||||
if (!empty(CDEK)) {
|
||||
// Initialized CDEK account data
|
||||
|
||||
// Initializing HTTP-client
|
||||
$client = new client(new guzzle, CDEK['account'], CDEK['secret']);
|
||||
|
||||
// Initializing buffer of packages
|
||||
$packages = [];
|
||||
|
||||
// Initializing packages from products
|
||||
foreach ($products as $product)
|
||||
$packages[] = package::create([
|
||||
'weight' => $product['weight'],
|
||||
'width' => $product['x'],
|
||||
'height' => $product['y'],
|
||||
'length' => $product['z']
|
||||
]);
|
||||
|
||||
// Deinitializing unnecessary variables
|
||||
unset($product);
|
||||
|
||||
if (!empty($packages)) {
|
||||
// Initialized packages
|
||||
|
||||
// Request
|
||||
$response = $client
|
||||
->calculator()
|
||||
->add(
|
||||
tariff::create([
|
||||
'type' => tariffs::TYPE_ECOMMERCE,
|
||||
'currency' => currencies::RUBLE, // @todo globalize this
|
||||
/* 'date' => $date->format(datetime::ISO8601_EXPANDED), */
|
||||
'date' => $date->format(datetime::ISO8601),
|
||||
'lang' => tariffs::LANG_RUS, // @todo globalize this
|
||||
'tariff_code' => $tariff,
|
||||
'from_location' => location::create([
|
||||
'code' => $from_location,
|
||||
'address' => $from_street,
|
||||
'country_code' => 'RU' // @todo globalize this
|
||||
]),
|
||||
'to_location' => location::create([
|
||||
'code' => $to_location,
|
||||
'address' => $to_street,
|
||||
'country_code' => 'RU' // @todo globalize this
|
||||
]),
|
||||
'packages' => $packages
|
||||
])
|
||||
);
|
||||
|
||||
if ($response->hasErrors()) {
|
||||
// Receied response with errors
|
||||
}
|
||||
|
||||
if ($response->isOk()) {
|
||||
// Received response
|
||||
|
||||
// Exit (success)
|
||||
return $client->formatBaseResponse($response, _tariff::class);
|
||||
}
|
||||
}
|
||||
|
||||
// Deinitializing unnecessary variables
|
||||
unset($client, $packages);
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\models\core,
|
||||
mirzaev\huesos\models\enumerations\delivery as company;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Model of settings
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class delivery extends core
|
||||
{
|
||||
/**
|
||||
* Ready
|
||||
*
|
||||
* @param company $company Delivery company
|
||||
* @param array $parameters Delivery company parameters
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return bool|null Is delivery data ready for creating order?
|
||||
*/
|
||||
public static function ready(company $company, array $parameters, array &$errors = []): ?bool
|
||||
{
|
||||
try {
|
||||
if (!empty($parameters)) {
|
||||
// Initialized delivery company data
|
||||
|
||||
if (
|
||||
!empty($parameters['type'])
|
||||
&& !empty($parameters['tariff'])
|
||||
&& (!empty($parameters['location']['name']) || (!empty($parameters['longitude']) && !empty($parameters['latitude'])))
|
||||
&& !empty($parameters['street'])
|
||||
) {
|
||||
// Initialized required parameters: company, type, tariff, location (or longitude with latitude), street
|
||||
|
||||
if ($parameters['type'] === 'door') {
|
||||
// Delivery to the door
|
||||
|
||||
if (!empty($parameters['apartament'])) {
|
||||
// Required parameters initialized: apartament
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
} else {
|
||||
// Not initialized required parameters
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
}
|
||||
} else if ($parameters['type'] === 'office') {
|
||||
// Delivery to the office
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
} else {
|
||||
// Not identified delivery type
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Not initialized required parameters
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
|
@ -1,341 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\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;
|
||||
|
||||
// Library for ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
mirzaev\arangodb\document,
|
||||
mirzaev\arangodb\enumerations\collection\type;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Model of entry
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class entry extends core implements document_interface, collection_interface
|
||||
{
|
||||
use document_trait;
|
||||
|
||||
/**
|
||||
* Name of the collection in ArangoDB
|
||||
*/
|
||||
final public const string COLLECTION = 'entry';
|
||||
|
||||
/**
|
||||
* Type of the collection in ArangoDB
|
||||
*/
|
||||
public const type TYPE = type::edge;
|
||||
|
||||
/**
|
||||
* Write an entry
|
||||
*
|
||||
* @param category|product $from Descendant document
|
||||
* @param category $to Ascendant document
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return string|null Identifier (_id) of instance of the entry document in ArangoDB, if created
|
||||
*/
|
||||
public static function write(
|
||||
category|product $from,
|
||||
category $to,
|
||||
array &$errors = []
|
||||
): string|null {
|
||||
try {
|
||||
if (collection::initialize($from::COLLECTION, $from::TYPE, errors: $errors)) {
|
||||
if (collection::initialize($to::COLLECTION, $to::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// Initialized collections
|
||||
|
||||
// Creating the entry and exit (success)
|
||||
return document::write(
|
||||
static::COLLECTION,
|
||||
[
|
||||
'_from' => $from->getId(),
|
||||
'_to' => $to->getId(),
|
||||
'version' => ROBOT_VERSION ?? '0.0.0'
|
||||
],
|
||||
errors: $errors
|
||||
);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . $to::TYPE->name . ' collection: ' . $to::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . $from::TYPE->name . ' collection: ' . $from::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ascendants
|
||||
*
|
||||
* Search for ascendants that are not descendants for anyone
|
||||
*
|
||||
* @param category|product $descendant Descendant document
|
||||
* @param string|null $return Return (AQL)
|
||||
* @param array $parameters Binded parameters for placeholders ['placeholder' => parameter]
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return array|null Ascendants that are not descendants for anyone, if found
|
||||
*/
|
||||
public static function ascendants(
|
||||
category|product $descendant,
|
||||
?string $return = 'DISTINCT ascendant',
|
||||
array $parameters = [],
|
||||
array &$errors = []
|
||||
): ?array {
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// Initialized the collection
|
||||
|
||||
// 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
|
||||
AQL,
|
||||
empty($return) ? 'DISTINCT d' : $return
|
||||
),
|
||||
[
|
||||
'@collection' => $descendant::COLLECTION,
|
||||
'@edge' => static::COLLECTION
|
||||
] + $parameters,
|
||||
errors: $errors
|
||||
)) {
|
||||
// Found ascendants
|
||||
|
||||
// Exit (success)
|
||||
return is_array($result) ? $result : [$result];
|
||||
} else return [];
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check existence of entry between documents
|
||||
*
|
||||
* @param category|product $from Descendant document
|
||||
* @param category $to Ascendant document
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return ?_document The entry edge, if found
|
||||
*/
|
||||
public static function check(
|
||||
category|product $from,
|
||||
category $to,
|
||||
array &$errors = []
|
||||
): ?_document {
|
||||
try {
|
||||
if (collection::initialize($from::COLLECTION, $from::TYPE, errors: $errors)) {
|
||||
if (collection::initialize($to::COLLECTION, $to::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// Initialized collections
|
||||
|
||||
if ($entry = collection::execute(
|
||||
<<<'AQL'
|
||||
FOR d IN @@collection
|
||||
FILTER d._from == @from && d._to == @to
|
||||
SORT d.updated DESC, d.created DESC
|
||||
LIMIT 1
|
||||
RETURN d
|
||||
AQL,
|
||||
[
|
||||
'@collection' => static::COLLECTION,
|
||||
'from' => $from->getId(),
|
||||
'to' => $to->getId()
|
||||
],
|
||||
errors: $errors
|
||||
)) {
|
||||
// Found the entry between $from and $to
|
||||
|
||||
// Exit (success)
|
||||
return is_array($entry) ? $entry[0] : $entry;
|
||||
} else return null;
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . $to::TYPE->name . ' collection: ' . $to::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . $from::TYPE->name . ' collection: ' . $from::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск вхождений (подкатегории или товары)
|
||||
*
|
||||
* Находит вхождения через ребро entry
|
||||
* Генерирует _type со значениями "category" и "product"
|
||||
* относительно того есть ли у документа ещё вложения
|
||||
* (подразумевается, что у product вложений быть не может)
|
||||
* Объединяет возвращаемые объекты документа с переменной _type
|
||||
*
|
||||
* @param category|product $document Ascendant document
|
||||
* @param string|null $filter Expression for filtering (AQL)
|
||||
* @param string|null $sort Expression for sorting (AQL)
|
||||
* @param int $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 Массив с найденными вхождениями (может быть пустым)
|
||||
*/
|
||||
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 {
|
||||
try {
|
||||
if (collection::initialize($document::COLLECTION, $document::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// Initialized collections
|
||||
|
||||
// Execute and exit (success)
|
||||
return is_array($result = collection::execute(
|
||||
sprintf(
|
||||
<<<'AQL'
|
||||
FOR v IN 1..%u INBOUND @document GRAPH @graph
|
||||
%s
|
||||
%s
|
||||
LIMIT @offset, @amount
|
||||
RETURN DISTINCT IS_SAME_COLLECTION(@category, v._id) ? MERGE(v, {_type: @category%s}) : MERGE(v, {_type: @product%s})
|
||||
AQL,
|
||||
$depth,
|
||||
empty($filter) ? '' : "FILTER $filter",
|
||||
empty($sort) ? '' : "SORT $sort",
|
||||
empty($categories_merge) ? '' : ", $categories_merge",
|
||||
empty($products_merge) ? '' : ", $products_merge"
|
||||
),
|
||||
[
|
||||
'category' => category::COLLECTION,
|
||||
'product' => product::COLLECTION,
|
||||
'graph' => 'catalog',
|
||||
'document' => $document->getId(),
|
||||
'offset' => --$page <= 0 ? $page = 0 : $page * $amount,
|
||||
'amount' => $amount
|
||||
] + $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);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Banish the document from the catalog
|
||||
*
|
||||
* Removes all entry edges associated with the document
|
||||
*
|
||||
* @param categoru|product $document Document for banishing
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function banish(category|product $document, array &$errors = []): void
|
||||
{
|
||||
try {
|
||||
if (collection::initialize($document::COLLECTION, $document::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// Initialized collections
|
||||
|
||||
// Execute and exit (success)
|
||||
collection::execute(
|
||||
<<<'AQL'
|
||||
FOR d IN @@collection
|
||||
// FILTER d._from == @_id || d._to == @_id
|
||||
FILTER d._from == @_id
|
||||
REMOVE d IN @@collection
|
||||
AQL,
|
||||
[
|
||||
'@collection' => static::COLLECTION,
|
||||
'_id' => $document->getId()
|
||||
],
|
||||
errors: $errors
|
||||
);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} else throw new exception('Failed to initialize ' . $document::TYPE->name . ' collection: ' . $document::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\enumerations;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\models\enumerations\language;
|
||||
|
||||
/**
|
||||
* Types of currencies by ISO 4217 standart
|
||||
*
|
||||
* @package mirzaev\huesos\models\enumerations
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
enum currency
|
||||
{
|
||||
case usd;
|
||||
case rub;
|
||||
|
||||
/**
|
||||
* Label
|
||||
*
|
||||
* Initialize label of the currency
|
||||
*
|
||||
* @param language|null $language Language into which to translate
|
||||
*
|
||||
* @return string Translated label of the currency
|
||||
*
|
||||
* @todo
|
||||
* 1. More currencies
|
||||
* 2. Cases???
|
||||
*/
|
||||
public function label(?language $language = language::en): string
|
||||
{
|
||||
// Exit (success)
|
||||
return match ($this) {
|
||||
currency::usd => match ($language) {
|
||||
language::en => 'Dollar',
|
||||
language::ru => 'Доллар'
|
||||
},
|
||||
currency::rub => match ($language) {
|
||||
language::en => 'Ruble',
|
||||
language::ru => 'Рубль'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Symbol
|
||||
*
|
||||
* Initialize symbol of the currency
|
||||
*
|
||||
* @return string Symbol of the currency
|
||||
*
|
||||
* @todo
|
||||
* 1. More currencies
|
||||
*/
|
||||
public function symbol(): string
|
||||
{
|
||||
// Exit (success)
|
||||
return match ($this) {
|
||||
currency::usd => '$',
|
||||
currency::rub => '₽'
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\enumerations;
|
||||
|
||||
/**
|
||||
* Types of deliveries
|
||||
*
|
||||
* @package mirzaev\huesos\models\enumerations
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
enum delivery: string
|
||||
{
|
||||
case cdek = 'CDEK';
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\enumerations;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\models\enumerations\language;
|
||||
|
||||
/**
|
||||
* Types of destination
|
||||
*
|
||||
* @package mirzaev\huesos\models\enumerations
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
enum destination
|
||||
{
|
||||
case office;
|
||||
case door;
|
||||
|
||||
/**
|
||||
* Label
|
||||
*
|
||||
* Initialize label of the destination
|
||||
*
|
||||
* @param language|null $language Language into which to translate
|
||||
*
|
||||
* @return string Translated label of the destination
|
||||
*/
|
||||
public function label(?language $language = language::en): string
|
||||
{
|
||||
// Exit (success)
|
||||
return match ($this) {
|
||||
destination::office => match ($language) {
|
||||
language::en => 'Office',
|
||||
language::ru => 'Пункт выдачи'
|
||||
},
|
||||
destination::door => match ($language) {
|
||||
language::en => 'Door',
|
||||
language::ru => 'До двери'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\enumerations;
|
||||
|
||||
/**
|
||||
* Types of languages by ISO 639-1 standart
|
||||
*
|
||||
* @package mirzaev\huesos\models\enumerations
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
enum language
|
||||
{
|
||||
case en;
|
||||
case ru;
|
||||
|
||||
/**
|
||||
* Label
|
||||
*
|
||||
* Initialize label of the language
|
||||
*
|
||||
* @param language|null $language Language into which to translate
|
||||
*
|
||||
* @return string Translated label of the language
|
||||
*
|
||||
* @todo
|
||||
* 1. More languages
|
||||
* 2. Cases???
|
||||
*/
|
||||
public function label(?language $language = language::en): string
|
||||
{
|
||||
// Exit (success)
|
||||
return match ($this) {
|
||||
language::en => match ($language) {
|
||||
language::en => 'English',
|
||||
language::ru => 'Английский'
|
||||
},
|
||||
language::ru => match ($language) {
|
||||
language::en => 'Russian',
|
||||
language::ru => 'Русский'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\enumerations;
|
||||
|
||||
/**
|
||||
* Types of session verification
|
||||
*
|
||||
* @package mirzaev\huesos\models\enumerations
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
enum session
|
||||
{
|
||||
case hash_only;
|
||||
case hash_else_address;
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\interfaces;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\enumerations\collection\type;
|
||||
|
||||
/**
|
||||
* Interface for implementing a collection from ArangoDB
|
||||
*
|
||||
* @package mirzaev\huesos\models\traits
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
interface collection
|
||||
{
|
||||
/**
|
||||
* Name of the collection in ArangoDB
|
||||
*/
|
||||
public const string COLLECTION = 'THIS_COLLECTION_SHOULD_NOT_EXIST';
|
||||
|
||||
/**
|
||||
* Type of the collection in ArangoDB
|
||||
*/
|
||||
public const type TYPE = type::document;
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\interfaces;
|
||||
|
||||
// Library для ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
/**
|
||||
* Interface for implementing a document instance from ArangoDB
|
||||
*
|
||||
* @param _document $document An instance of the ArangoDB document from ArangoDB (protected readonly)
|
||||
*
|
||||
* @package mirzaev\huesos\models\traits
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
interface document
|
||||
{
|
||||
/**
|
||||
* Write
|
||||
*
|
||||
* Write a property into an instance of the ArangoDB document
|
||||
*
|
||||
* @param string $name Name of the property
|
||||
* @param mixed $value Content of the property
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __set(string $name, mixed $value = null): void;
|
||||
|
||||
/**
|
||||
* Read
|
||||
*
|
||||
* Read a property from an instance of the ArangoDB docuemnt
|
||||
*
|
||||
* @param string $name Name of the property
|
||||
*
|
||||
* @return mixed Content of the property
|
||||
*/
|
||||
public function __get(string $name): mixed;
|
||||
|
||||
|
||||
/**
|
||||
* Delete
|
||||
*
|
||||
* Deinitialize the property in an instance of the ArangoDB document
|
||||
*
|
||||
* @param string $name Name of the property
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __unset(string $name): void;
|
||||
|
||||
/**
|
||||
* Check of initialization
|
||||
*
|
||||
* Check of initialization of the property into an instance of the ArangoDB document
|
||||
*
|
||||
* @param string $name Name of the property
|
||||
*
|
||||
* @return bool The property is initialized?
|
||||
*/
|
||||
public function __isset(string $name): bool;
|
||||
|
||||
/**
|
||||
* Execute a method
|
||||
*
|
||||
* Execute a method from an instance of the ArangoDB document
|
||||
*
|
||||
* @param string $name Name of the method
|
||||
* @param array $arguments Arguments for the method
|
||||
*
|
||||
* @return mixed Result of execution of the method
|
||||
*/
|
||||
public function __call(string $name, array $arguments = []): mixed;
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\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;
|
||||
|
||||
/**
|
||||
* Model of menu
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class menu extends core implements document_interface, collection_interface
|
||||
{
|
||||
use document_trait;
|
||||
|
||||
/**
|
||||
* Name of the collection in ArangoDB
|
||||
*/
|
||||
final public const string COLLECTION = 'menu';
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\models\core,
|
||||
mirzaev\huesos\models\cart,
|
||||
mirzaev\huesos\models\account,
|
||||
mirzaev\huesos\models\reservation,
|
||||
mirzaev\huesos\models\traits\document as document_trait,
|
||||
mirzaev\huesos\models\interfaces\document as document_interface,
|
||||
mirzaev\huesos\models\interfaces\collection as collection_interface,
|
||||
mirzaev\huesos\models\enumerations\language,
|
||||
mirzaev\huesos\models\enumerations\currency;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
mirzaev\arangodb\document;
|
||||
|
||||
// Library for ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Model of order
|
||||
*
|
||||
* @uses !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
* @package mirzaev\huesos\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class order extends core implements document_interface, collection_interface
|
||||
{
|
||||
use document_trait;
|
||||
|
||||
/**
|
||||
* Name of the collection in ArangoDB
|
||||
*/
|
||||
final public const string COLLECTION = 'order';
|
||||
|
||||
/**
|
||||
* Cart
|
||||
*
|
||||
* Search for the connected cart
|
||||
*
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return cart|null The connected cart, if found
|
||||
*/
|
||||
public function cart(array &$errors = []): ?cart
|
||||
{
|
||||
try {
|
||||
if ($result = collection::execute(
|
||||
<<<'AQL'
|
||||
FOR v IN 1..1 INBOUND @order GRAPH users
|
||||
FILTER PARSE_IDENTIFIER(v._id).collection == @cart
|
||||
LIMIT 1
|
||||
return v
|
||||
AQL,
|
||||
[
|
||||
'order' => $this->document->getId(),
|
||||
'cart' => cart::COLLECTION
|
||||
],
|
||||
errors: $errors
|
||||
)) {
|
||||
// Found the instance of the cart connected to the order
|
||||
|
||||
// Initializing the object
|
||||
$cart = new cart;
|
||||
|
||||
if (method_exists($cart, '__document')) {
|
||||
// Object can implement a document from ArangoDB
|
||||
|
||||
// Writing the instance of the cart document from ArangoDB to the implement object
|
||||
$cart->__document($result);
|
||||
|
||||
// Exit (success)
|
||||
return $cart;
|
||||
}
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Account
|
||||
*
|
||||
* Search for the connected account
|
||||
*
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return account|null The connected account, if found
|
||||
*/
|
||||
public function account(array &$errors = []): ?account
|
||||
{
|
||||
try {
|
||||
if ($result = collection::execute(
|
||||
<<<'AQL'
|
||||
FOR v IN 1..1 OUTBOUND @order GRAPH users
|
||||
FILTER PARSE_IDENTIFIER(v._id).collection == @account
|
||||
LIMIT 1
|
||||
return v
|
||||
AQL,
|
||||
[
|
||||
'order' => $this->document->getId(),
|
||||
'account' => account::COLLECTION
|
||||
],
|
||||
errors: $errors
|
||||
)) {
|
||||
// Found the instance of the account connected to the account
|
||||
|
||||
// Initializing the object
|
||||
$account = new account;
|
||||
|
||||
if (method_exists($account, '__document')) {
|
||||
// Object can implement a document from ArangoDB
|
||||
|
||||
// Implementinf parameters
|
||||
if (isset($result->language)) $result->language = language::{$result->language};
|
||||
if (isset($result->currency)) $result->currency = currency::{$result->currency};
|
||||
|
||||
// Writing the instance of the account document from ArangoDB to the implement object
|
||||
$account->__document($result);
|
||||
|
||||
// Exit (success)
|
||||
return $account;
|
||||
}
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,298 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\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;
|
||||
|
||||
// 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 a product
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class product extends core implements document_interface, collection_interface
|
||||
{
|
||||
use document_trait;
|
||||
|
||||
/**
|
||||
* Name of the collection in ArangoDB
|
||||
*/
|
||||
final public const string COLLECTION = 'product';
|
||||
|
||||
/**
|
||||
* Write a product
|
||||
*
|
||||
* @param int $identifier Identifier (unique)
|
||||
* @param array $name Name [['en' => value], ['ru' => значение]]
|
||||
* @param array|null $description Description [['en' => value], ['ru' => значение]]
|
||||
* @param float $cost Cost
|
||||
* @param float $weight Weight
|
||||
* @param array $dimensions Dimensions ['x' => 0.0, 'y' => 0.0, 'z' => 0.0]
|
||||
* @param array|null $brand Brand [['en' => value], ['ru' => значение]]
|
||||
* @param array|null $compatibility Compatibility [['en' => value], ['ru' => значение]]
|
||||
* @param array $images Images (first will be thumbnail)
|
||||
* @param int|null $position Position for sorting in the catalog (ASC)
|
||||
* @param array $data Data
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return string|null Identifier (_id) of instance of the product document in ArangoDB, if created
|
||||
*
|
||||
* @todo
|
||||
* 1. Bind parameters
|
||||
*/
|
||||
public static function write(
|
||||
int $identifier,
|
||||
array $name = [['en' => 'ERROR']],
|
||||
?array $description = [['en' => 'ERROR']],
|
||||
array $cost = [['usd' => 0]],
|
||||
float $weight = 0,
|
||||
array $dimensions = ['x' => 0, 'y' => 0, 'z' => 0],
|
||||
?array $brand = [['en' => 'ERROR']],
|
||||
?array $compatibility = [['en' => 'ERROR']],
|
||||
array $images = [],
|
||||
?int $position = null,
|
||||
array $data = [],
|
||||
array &$errors = []
|
||||
): string|null {
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// Initialized the collection
|
||||
|
||||
// Writing in ArangoDB and exit (success)
|
||||
return document::write(
|
||||
static::COLLECTION,
|
||||
[
|
||||
'identifier' => $identifier,
|
||||
'name' => $name,
|
||||
'description' => $description,
|
||||
'cost' => $cost ?? 0,
|
||||
'weight' => $weight ?? 0,
|
||||
'dimensions' => [
|
||||
'x' => $dimensions['x'] ?? 0,
|
||||
'y' => $dimensions['y'] ?? 0,
|
||||
'z' => $dimensions['z'] ?? 0,
|
||||
],
|
||||
'brand' => $brand,
|
||||
'compatibility' => $compatibility,
|
||||
'images' => $images,
|
||||
'position' => $position,
|
||||
'version' => ROBOT_VERSION
|
||||
] + $data,
|
||||
errors: $errors
|
||||
);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read products
|
||||
*
|
||||
* @param string|null $search Search (text)
|
||||
* @param string|null $filter Flter (AQL)
|
||||
* @param string|null $sort Sort (AQL)
|
||||
* @param int $page Page
|
||||
* @param int $amount Amount per page
|
||||
* @param string|null $return Return (AQL)
|
||||
* @param language|null $language Language
|
||||
* @param currency|null $currency Currency
|
||||
* @param array $parameters Binded parameters for placeholders ['placeholder' => parameter]
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return array|static Found products or instance of the product from ArangoDB (can be empty)
|
||||
*
|
||||
* @todo убрать language и currency
|
||||
*/
|
||||
public static function read(
|
||||
?string $search = null,
|
||||
?string $filter = 'd.deleted != true && d.hidden != true',
|
||||
?string $sort = 'd.position ASC, d.created DESC',
|
||||
int $page = 1,
|
||||
int $amount = 100,
|
||||
?string $return = 'DISTINCT d',
|
||||
?language $language = null,
|
||||
?currency $currency = null,
|
||||
array $parameters = [],
|
||||
array &$errors = []
|
||||
): array|static {
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// Initialized the collection
|
||||
|
||||
// Initializing of the language parameter
|
||||
if ($language instanceof language) $parameters['language'] = $language->name;
|
||||
|
||||
// Initializing of the currency parameter
|
||||
if ($currency instanceof currency) $parameters['currency'] = $currency->name;
|
||||
|
||||
// Initializing parameters for search
|
||||
if ($search) $parameters += [
|
||||
'search' => $search,
|
||||
'analyzer' => 'text_' . $language->name ?? language::en->name
|
||||
];
|
||||
|
||||
// Search for products
|
||||
$result = collection::execute(
|
||||
sprintf(
|
||||
<<<'AQL'
|
||||
FOR d IN @@collection %s
|
||||
%s
|
||||
%s
|
||||
LIMIT @offset, @amount
|
||||
RETURN %s
|
||||
AQL,
|
||||
empty($search) ? '' : <<<'AQL'
|
||||
SEARCH
|
||||
LEVENSHTEIN_MATCH(
|
||||
d.name.@language,
|
||||
TOKENS(@search, @analyzer)[0],
|
||||
1,
|
||||
false
|
||||
) OR
|
||||
LEVENSHTEIN_MATCH(
|
||||
d.description.@language,
|
||||
TOKENS(@search, @analyzer)[0],
|
||||
1,
|
||||
false
|
||||
) OR
|
||||
LEVENSHTEIN_MATCH(
|
||||
d.compatibility.@language,
|
||||
TOKENS(@search, @analyzer)[0],
|
||||
1,
|
||||
false
|
||||
)
|
||||
AQL,
|
||||
empty($filter) ? '' : "FILTER $filter",
|
||||
empty($search) ? (empty($sort) ? '' : "SORT $sort") : (empty($sort) ? "SORT BM25(d) DESC" : "SORT BM25(d) DESC, $sort"),
|
||||
empty($return) ? 'DISTINCT d' : $return
|
||||
),
|
||||
[
|
||||
'@collection' => empty($search) ? static::COLLECTION : static::COLLECTION . 's_search',
|
||||
'offset' => --$page <= 0 ? $page = 0 : $page * $amount,
|
||||
'amount' => $amount,
|
||||
] + $parameters,
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
if ($amount === 1 && $result instanceof _document) {
|
||||
// Found the product @todo need to rebuild this
|
||||
|
||||
// Initializing the object
|
||||
$product = new static;
|
||||
|
||||
if (method_exists($product, '__document')) {
|
||||
// Object can implement a document from ArangoDB
|
||||
|
||||
// Writing the instance of product document from ArangoDB to the implement object
|
||||
$product->__document($result);
|
||||
|
||||
// Exit (success)
|
||||
return $product;
|
||||
} else throw new exception('Class ' . static::class . ' does not implement a document from ArangoDB');
|
||||
} else if (!empty($result)) {
|
||||
// Found products
|
||||
|
||||
// Exit (success)
|
||||
return is_array($result) ? $result : [$result];
|
||||
}
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect parameter from all products
|
||||
*
|
||||
* @param string $return Return (AQL path)
|
||||
* @param array $products Array with products system identifiers ["_id", "_id", "_id"...]
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return array Array with found unique parameter values from all products (can be empty)
|
||||
*/
|
||||
public static function collect(
|
||||
string $return = 'd._key',
|
||||
array $products = [],
|
||||
language $language = language::en,
|
||||
array $parameters = [],
|
||||
array &$errors = []
|
||||
): array {
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// Initialized the collection
|
||||
|
||||
if ($result = collection::execute(
|
||||
sprintf(
|
||||
<<<'AQL'
|
||||
FOR d IN @@collection
|
||||
%s
|
||||
RETURN DISTINCT %s
|
||||
AQL,
|
||||
empty($products) ? '' : 'FILTER POSITION(["' . implode('", "', $products) . '"], d._id)',
|
||||
empty($return) ? 'd._key' : $return
|
||||
),
|
||||
[
|
||||
'@collection' => static::COLLECTION,
|
||||
'language' => $language->name,
|
||||
] + $parameters,
|
||||
errors: $errors
|
||||
)) {
|
||||
// Found parameters
|
||||
|
||||
// Exit (success)
|
||||
return is_array($result) ? $result : [$result];
|
||||
} else return [];
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return [];
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\models\core,
|
||||
mirzaev\huesos\models\cart,
|
||||
mirzaev\huesos\models\traits\document as document_trait,
|
||||
mirzaev\huesos\models\interfaces\document as document_interface,
|
||||
mirzaev\huesos\models\interfaces\collection as collection_interface;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\enumerations\collection\type;
|
||||
|
||||
/**
|
||||
* Model of reservtion
|
||||
*
|
||||
* @used-by cart
|
||||
* @package mirzaev\huesos\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class reservation extends core implements document_interface, collection_interface
|
||||
{
|
||||
use document_trait;
|
||||
|
||||
/**
|
||||
* Name of the collection in ArangoDB
|
||||
*/
|
||||
final public const string COLLECTION = 'reservation';
|
||||
|
||||
/**
|
||||
* Type of the collection in ArangoDB
|
||||
*/
|
||||
public const type TYPE = type::edge;
|
||||
}
|
|
@ -1,296 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\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;
|
||||
|
||||
// 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 a session
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class session extends core implements document_interface, collection_interface
|
||||
{
|
||||
use status, document_trait, buffer, cart {
|
||||
buffer::write as write;
|
||||
cart::initialize as cart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the collection in ArangoDB
|
||||
*/
|
||||
final public const string COLLECTION = 'session';
|
||||
|
||||
/**
|
||||
* Type of sessions verification
|
||||
*/
|
||||
final public const verification VERIFICATION = verification::hash_else_address;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Initialize session and write into 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
|
||||
*/
|
||||
public function __construct(?string $hash = null, ?int $expires = null, array &$errors = [])
|
||||
{
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// Initialized the collection
|
||||
|
||||
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)) {
|
||||
// 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 {
|
||||
// Not found the instance of the ArangoDB document of session
|
||||
|
||||
// Initializing a new session and write they into ArangoDB
|
||||
$_id = document::write(
|
||||
static::COLLECTION,
|
||||
[
|
||||
'active' => true,
|
||||
'expires' => $expires ?? time() + 604800,
|
||||
'address' => $_SERVER['REMOTE_ADDR'],
|
||||
'x-forwarded-for' => $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null,
|
||||
'referer' => $_SERVER['HTTP_REFERER'] ?? null,
|
||||
'useragent' => $_SERVER['HTTP_USER_AGENT'] ?? null
|
||||
]
|
||||
);
|
||||
|
||||
if ($session = collection::execute(
|
||||
<<<'AQL'
|
||||
FOR d IN @@collection
|
||||
FILTER d._id == @_id && d.expires > @time && d.active == true
|
||||
RETURN d
|
||||
AQL,
|
||||
[
|
||||
'@collection' => static::COLLECTION,
|
||||
'_id' => $_id,
|
||||
'time' => time()
|
||||
],
|
||||
errors: $errors
|
||||
)) {
|
||||
// Found the instance of just created new session
|
||||
|
||||
// Generating a hash and write into the instance of the ArangoDB document of session property
|
||||
$session->hash = sodium_bin2hex(sodium_crypto_generichash($_id));
|
||||
|
||||
if (document::update($session, errors: $errors)) {
|
||||
// Writed into ArangoDB
|
||||
|
||||
// Writing instance of the session document from ArangoDB to the property of the implementing object
|
||||
$this->__document($session);
|
||||
} else throw new exception('Failed to write the session data');
|
||||
} else throw new exception('Failed to create or find just created session');
|
||||
}
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a connected account
|
||||
*
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return account|null An object implements the instance of the account document from ArangoDB, if found
|
||||
*/
|
||||
public function account(array &$errors = []): ?account
|
||||
{
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(connect::COLLECTION, connect::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(account::COLLECTION, account::TYPE, errors: $errors)) {
|
||||
// Initialized collections
|
||||
|
||||
// Search for connected account
|
||||
$result = collection::execute(
|
||||
<<<AQL
|
||||
FOR v IN INBOUND @session GRAPH @graph
|
||||
FILTER IS_SAME_COLLECTION(@collection, v._id)
|
||||
SORT v.created DESC
|
||||
LIMIT 1
|
||||
RETURN v
|
||||
AQL,
|
||||
[
|
||||
'graph' => 'users',
|
||||
'collection' => account::COLLECTION,
|
||||
'session' => $this->getId()
|
||||
],
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
if ($result instanceof _document) {
|
||||
// Found the account
|
||||
|
||||
// Initializing the object
|
||||
$account = new account;
|
||||
|
||||
if (method_exists($account, '__document')) {
|
||||
// Object can implement a document from ArangoDB
|
||||
|
||||
// Abstractioning of parameters
|
||||
if (isset($result->language)) $result->language = language::{$result->language};
|
||||
if (isset($result->currency)) $result->currency = currency::{$result->currency};
|
||||
|
||||
// Writing the instance of account document from ArangoDB to the implement object
|
||||
$account->__document($result);
|
||||
|
||||
// Exit (success)
|
||||
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);
|
||||
} 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 by hash
|
||||
*
|
||||
* Search for the session in ArangoDB by hash
|
||||
*
|
||||
* @param string $hash Hash of the session in ArangoDB
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return _document|null instance of document of the session in ArangoDB
|
||||
*/
|
||||
public static function hash(string $hash, array &$errors = []): ?_document
|
||||
{
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// Collection initialized
|
||||
|
||||
// Search the session data in ArangoDB
|
||||
return collection::execute(
|
||||
<<<'AQL'
|
||||
FOR d IN @@collection
|
||||
FILTER d.hash == @hash && d.expires > @time && d.active == true
|
||||
RETURN d
|
||||
AQL,
|
||||
[
|
||||
'@collection' => static::COLLECTION,
|
||||
'hash' => $hash,
|
||||
'time' => time()
|
||||
],
|
||||
errors: $errors
|
||||
);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search by IP-address
|
||||
*
|
||||
* Search for the session in ArangoDB by IP-address
|
||||
*
|
||||
* @param string $address IP-address writed to the session in ArangoDB
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return _document|null instance of document of the session in ArangoDB
|
||||
*/
|
||||
public static function address(string $address, array &$errors = []): ?_document
|
||||
{
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// Collection initialized
|
||||
|
||||
// Search the session data in ArangoDB
|
||||
return collection::execute(
|
||||
<<<'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,
|
||||
'address' => $address,
|
||||
'time' => time()
|
||||
],
|
||||
errors: $errors
|
||||
);
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\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;
|
||||
|
||||
// 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 settings
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class settings extends core implements document_interface, collection_interface
|
||||
{
|
||||
use document_trait;
|
||||
|
||||
/**
|
||||
* Name of the collection in ArangoDB
|
||||
*/
|
||||
final public const string COLLECTION = 'settings';
|
||||
|
||||
/**
|
||||
* Search for active settings
|
||||
*
|
||||
* @param array|null $create Данные для создания, если настройки не найдены
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return static|null Object implements the instance of settngs document from ArangoDB
|
||||
*/
|
||||
public static function active(array|null $create = null, array &$errors = []): static|null
|
||||
{
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// Initialized the collection
|
||||
|
||||
// Search for active settings
|
||||
$result = collection::execute(
|
||||
<<<'AQL'
|
||||
FOR d IN @@collection
|
||||
FILTER d.status == 'active'
|
||||
SORT d.updated DESC
|
||||
LIMIT 1
|
||||
RETURN d
|
||||
AQL,
|
||||
[
|
||||
'@collection' => static::COLLECTION
|
||||
],
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
if ($result instanceof _document) {
|
||||
// Found active settings
|
||||
|
||||
// Initializing the object
|
||||
$settings = new static;
|
||||
|
||||
if (method_exists($settings, '__document')) {
|
||||
// Object can implement a document from ArangoDB
|
||||
|
||||
// Abstractioning of parameters
|
||||
if (isset($result->language)) $result->language = language::{$result->language};
|
||||
if (isset($result->currency)) $result->currency = currency::{$result->currency};
|
||||
|
||||
// Writing the instance of settings document from ArangoDB to the implement object
|
||||
$settings->__document($result);
|
||||
|
||||
// Exit (success)
|
||||
return $settings;
|
||||
} else throw new exception('Class ' . static::class . ' does not implement a document from ArangoDB');
|
||||
} else if ($create) {
|
||||
// Not found active settings and requested their creating
|
||||
|
||||
// Creating a settings
|
||||
document::write(static::COLLECTION, ['status' => 'active'] + $create, errors: $errors);
|
||||
|
||||
// Re-search (without creating) and exit (success || fail)
|
||||
return static::active(errors: $errors);
|
||||
} else throw new exception('Active settings not found');
|
||||
} else throw new exception('Failed to initialize ' . static::TYPE->name . ' collection: ' . static::COLLECTION);
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\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;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
mirzaev\arangodb\document;
|
||||
|
||||
// Library для ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// Built-in libraries
|
||||
use exception,
|
||||
datetime;
|
||||
|
||||
/**
|
||||
* Model of a suspension
|
||||
*
|
||||
* @package mirzaev\huesos\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class suspension extends core implements document_interface, collection_interface
|
||||
{
|
||||
use document_trait;
|
||||
|
||||
/**
|
||||
* Name of the collection in ArangoDB
|
||||
*/
|
||||
final public const string COLLECTION = 'suspension';
|
||||
|
||||
/**
|
||||
* Search for active suspension
|
||||
*
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return static|null Object implements the instance of suspension from ArangoDB
|
||||
*/
|
||||
public static function search(array &$errors = []): static|null
|
||||
{
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// Initialized the collection
|
||||
|
||||
// Search for active suspension
|
||||
$result = collection::execute(
|
||||
<<<'AQL'
|
||||
FOR d IN @@collection
|
||||
FILTER d.end > @time
|
||||
SORT d.end DESC
|
||||
LIMIT 1
|
||||
RETURN d
|
||||
AQL,
|
||||
[
|
||||
'@collection' => static::COLLECTION,
|
||||
'time' => time()
|
||||
],
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
if ($result instanceof _document) {
|
||||
// Found active settings
|
||||
|
||||
// Initializing the object
|
||||
$suspension = new static;
|
||||
|
||||
if (method_exists($suspension, '__document')) {
|
||||
// Object can implement a document from ArangoDB
|
||||
|
||||
// Writing the instance of suspension document from ArangoDB to the implement object
|
||||
$suspension->__document($result);
|
||||
|
||||
// Exit (success)
|
||||
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);
|
||||
} 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate message about remaining time
|
||||
*
|
||||
* @param language|null $language Language of the generated text (otherwise used from settings.language)
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return string|null Text: "? days, ? hours and ? minutes"
|
||||
*/
|
||||
public function message(?language $language = language::en, array &$errors = []): ?string
|
||||
{
|
||||
try {
|
||||
// Initializing the time until the suspension ends
|
||||
$difference = date_diff(new datetime('@' . $this->document->end), new datetime());
|
||||
|
||||
// Generate text about remaining time and exit (success)
|
||||
return sprintf(
|
||||
'%u %s, %u %s и %u %s',
|
||||
$difference->d,
|
||||
match ($difference->d > 20 ? $difference->d % 10 : $difference->d % 100) {
|
||||
1 => match ($language) {
|
||||
language::en => 'day',
|
||||
language::ru => 'день',
|
||||
default => 'day'
|
||||
},
|
||||
2, 3, 4 => match ($language) {
|
||||
language::en => 'days',
|
||||
language::ru => 'дня',
|
||||
default => 'days'
|
||||
},
|
||||
default => match ($language) {
|
||||
language::en => 'days',
|
||||
language::ru => 'дней',
|
||||
default => 'days'
|
||||
}
|
||||
},
|
||||
$difference->h,
|
||||
match ($difference->h > 20 ? $difference->h % 10 : $difference->h % 100) {
|
||||
1 => match ($language) {
|
||||
language::en => 'hours',
|
||||
language::ru => 'час',
|
||||
default => 'hour'
|
||||
},
|
||||
2, 3, 4 => match ($language) {
|
||||
language::en => 'hours',
|
||||
language::ru => 'часа',
|
||||
default => 'hours'
|
||||
},
|
||||
default => match ($language) {
|
||||
language::en => 'hours',
|
||||
language::ru => 'часов',
|
||||
default => 'hours'
|
||||
}
|
||||
},
|
||||
$difference->i,
|
||||
match ($difference->i > 20 ? $difference->i % 10 : $difference->i % 100) {
|
||||
1 => match ($language) {
|
||||
language::en => 'minute',
|
||||
language::ru => 'минута',
|
||||
default => 'minute'
|
||||
},
|
||||
2, 3, 4 => match ($language) {
|
||||
language::en => 'minutes',
|
||||
language::ru => 'минуты',
|
||||
default => 'minutes'
|
||||
},
|
||||
default => match ($language) {
|
||||
language::en => 'minutes',
|
||||
language::ru => 'минут',
|
||||
default => 'minutes'
|
||||
}
|
||||
}
|
||||
);
|
||||
} 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;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,81 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\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;
|
||||
|
||||
// Library for ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
mirzaev\arangodb\document;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Buffer
|
||||
*
|
||||
* Storage of data in the document from ArangoDB
|
||||
*
|
||||
* @param static COLLECTION Name of the collection in ArangoDB
|
||||
* @param static TYPE Type of the collection in ArangoDB
|
||||
*
|
||||
* @uses collection_interface
|
||||
* @package mirzaev\huesos\models\traits
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
trait buffer
|
||||
{
|
||||
/**
|
||||
* Write to buffer of the document
|
||||
*
|
||||
* @param array $data Data for writing (merge)
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return bool Is data has written into the document from ArangoDB?
|
||||
*/
|
||||
public function write(array $data, array &$errors = []): bool
|
||||
{
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
// 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');
|
||||
|
||||
// Writing data into buffer of the instance of the document from ArangoDB
|
||||
$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');
|
||||
|
||||
// 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; */
|
||||
|
||||
// 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);
|
||||
} 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;
|
||||
}
|
||||
}
|
|
@ -1,151 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\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;
|
||||
|
||||
// Library для ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
mirzaev\arangodb\document;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Cart
|
||||
*
|
||||
* Cart for a document from ArangoDB
|
||||
*
|
||||
* @uses collection_interface
|
||||
* @uses document_interface
|
||||
* @package mirzaev\huesos\models\traits
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
trait cart
|
||||
{
|
||||
use document_trait;
|
||||
|
||||
/**
|
||||
* Cart
|
||||
*
|
||||
* Search for a connected cart
|
||||
*
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return model|null An object implements the instance of the cart document from ArangoDB, if found
|
||||
*/
|
||||
public function initialize(array &$errors = []): ?model
|
||||
{
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(connect::COLLECTION, connect::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(model::COLLECTION, model::TYPE, errors: $errors)) {
|
||||
// Initialized collections
|
||||
|
||||
// Search for connected cart
|
||||
$result = collection::execute(
|
||||
<<<AQL
|
||||
FOR v IN 1..1 INBOUND @document GRAPH @graph
|
||||
FILTER IS_SAME_COLLECTION(@cart, v._id) && v.active == true && v.ordered != true
|
||||
SORT v.updated DESC, v.created DESC
|
||||
LIMIT 1
|
||||
RETURN v
|
||||
AQL,
|
||||
[
|
||||
'cart' => model::COLLECTION,
|
||||
'graph' => 'users',
|
||||
'document' => $this->document->getId()
|
||||
],
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
if ($result instanceof _document) {
|
||||
// Found the cart
|
||||
|
||||
// Initializing the object
|
||||
$cart = new model;
|
||||
|
||||
if (method_exists($cart, '__document')) {
|
||||
// Object can implement a document from ArangoDB
|
||||
|
||||
// Writing the instance of cart document from ArangoDB to the implement object
|
||||
$cart->__document($result);
|
||||
|
||||
// Exit (success)
|
||||
return $cart;
|
||||
}
|
||||
} else {
|
||||
// Not found the cart
|
||||
|
||||
// Initializing a new cart and write they into ArangoDB
|
||||
$_id = document::write(
|
||||
model::COLLECTION,
|
||||
[
|
||||
'active' => true,
|
||||
]
|
||||
);
|
||||
|
||||
if ($result = collection::execute(
|
||||
<<<'AQL'
|
||||
FOR d IN @@collection
|
||||
FILTER d._id == @_id && d.active == true
|
||||
RETURN d
|
||||
AQL,
|
||||
[
|
||||
'@collection' => model::COLLECTION,
|
||||
'_id' => $_id
|
||||
],
|
||||
errors: $errors
|
||||
)) {
|
||||
// Found the instance of just created new cart
|
||||
|
||||
// Initializing the object
|
||||
$cart = new model;
|
||||
|
||||
if (method_exists($cart, '__document')) {
|
||||
// Object can implement a document from ArangoDB
|
||||
|
||||
// Writing the instance of cart document from ArangoDB to the implement object
|
||||
$cart->__document($result);
|
||||
|
||||
// Connecting the cart to the document
|
||||
$connected = $this->connect($cart, $errors);
|
||||
|
||||
if ($connected) {
|
||||
// The cart has been connected to the document
|
||||
|
||||
// Exit (success)
|
||||
return $cart;
|
||||
} else throw new exception('Failed to connect the ' . model::class . ' to the ' . static::class);
|
||||
} 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);
|
||||
} 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;
|
||||
}
|
||||
}
|
|
@ -1,236 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\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;
|
||||
|
||||
// Library для ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\connection as arangodb,
|
||||
mirzaev\arangodb\document as framework_document,
|
||||
mirzaev\arangodb\collection;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Trait for implementing a document instance from ArangoDB
|
||||
*
|
||||
* @var protected readonly _document|null $document An instance of the ArangoDB document
|
||||
*
|
||||
* @uses document_interface
|
||||
* @package mirzaev\huesos\models\traits
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
trait document
|
||||
{
|
||||
/**
|
||||
* An instance of the ArangoDB document from ArangoDB
|
||||
*/
|
||||
protected readonly _document $document;
|
||||
|
||||
/**
|
||||
* Constructor of an instance
|
||||
*
|
||||
* @param bool $initialize Initialize a model?
|
||||
* @param ?arangodb $arangodb Instance of a session of ArangoDB
|
||||
* @param _document|null|false $document An instance of the ArangoDB document
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
bool $initialize = true,
|
||||
?arangodb $arangodb = null,
|
||||
_document|null|false $document = false
|
||||
) {
|
||||
// For the extends system
|
||||
parent::__construct($initialize, $arangodb);
|
||||
|
||||
// Writing to the property
|
||||
if ($document instanceof _document) $this->__document($document);
|
||||
else if ($document === null) throw new exception('Failed to initialize an instance of the document from ArangoDB');
|
||||
}
|
||||
|
||||
/**
|
||||
* Write or read document
|
||||
*
|
||||
* @param _document|null $document Instance of document from ArangoDB
|
||||
*
|
||||
* @return _document|null Instance of document from ArangoDB
|
||||
*/
|
||||
public function __document(?_document $document = null): ?_document
|
||||
{
|
||||
// Writing a property storing a document instance to ArangoDB
|
||||
if ($document) $this->document ??= $document;
|
||||
|
||||
// Read a property storing a document instance to ArangoDB and exit (success)
|
||||
return $this->document ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function connect(collection_interface $document, array &$errors = []): ?string
|
||||
{
|
||||
try {
|
||||
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
|
||||
if (collection::initialize(connect::COLLECTION, connect::TYPE, errors: $errors)) {
|
||||
if (collection::initialize($document::COLLECTION, $document::TYPE, errors: $errors)) {
|
||||
// Initialized collections
|
||||
|
||||
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,
|
||||
[
|
||||
'@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);
|
||||
} 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 a property into an instance of the ArangoDB document
|
||||
*
|
||||
* @param string $name Name of the property
|
||||
* @param mixed $value Content of the property
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __set(string $name, mixed $value = null): void
|
||||
{
|
||||
// Writing to the property into an instance of the ArangoDB document and exit (success)
|
||||
$this->document->{$name} = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read
|
||||
*
|
||||
* Read a property from an instance of the ArangoDB docuemnt
|
||||
*
|
||||
* @param string $name Name of the property
|
||||
*
|
||||
* @return mixed Content of the property
|
||||
*/
|
||||
public function __get(string $name): mixed
|
||||
{
|
||||
// Read a property from an instance of the ArangoDB document and exit (success)
|
||||
return match ($name) {
|
||||
default => $this->document->{$name}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete
|
||||
*
|
||||
* Deinitialize the property in an instance of the ArangoDB document
|
||||
*
|
||||
* @param string $name Name of the property
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __unset(string $name): void
|
||||
{
|
||||
// Delete the property in an instance of the ArangoDB document and exit (success)
|
||||
unset($this->document->{$name});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check of initialization
|
||||
*
|
||||
* Check of initialization of the property into an instance of the ArangoDB document
|
||||
*
|
||||
* @param string $name Name of the property
|
||||
*
|
||||
* @return bool The property is initialized?
|
||||
*/
|
||||
public function __isset(string $name): bool
|
||||
{
|
||||
// Check of initializatio nof the property and exit (success)
|
||||
return isset($this->document->{$name});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a method
|
||||
*
|
||||
* Execute a method from an instance of the ArangoDB document
|
||||
*
|
||||
* @param string $name Name of the method
|
||||
* @param array $arguments Arguments for the method
|
||||
*
|
||||
* @return mixed Result of execution of the method
|
||||
*/
|
||||
public function __call(string $name, array $arguments = []): mixed
|
||||
{
|
||||
// Execute the method and exit (success)
|
||||
return method_exists($this->document, $name) ? $this->document->{$name}($arguments) ?? null : null;
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\traits;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Trait for initialization of files handlers
|
||||
*
|
||||
* @package mirzaev\huesos\models\traits
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
trait files
|
||||
{
|
||||
/**
|
||||
* Delete files recursively
|
||||
*
|
||||
* @param string $directory Directory
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function delete(string $directory, array &$errors = []): void
|
||||
{
|
||||
try {
|
||||
if (file_exists($directory)) {
|
||||
// Directory exists
|
||||
|
||||
// Deleting descendant files and directories (enter to the recursion)
|
||||
foreach (scandir($directory) as $file) {
|
||||
if ($file === '.' || $file === '..') continue;
|
||||
else if (is_dir("$directory/$file")) static::delete("$directory/$file", $errors);
|
||||
else unlink("$directory/$file");
|
||||
}
|
||||
|
||||
// Deleting the directory
|
||||
rmdir($directory);
|
||||
|
||||
// Exit (success)
|
||||
return;
|
||||
} else throw new exception('Directory does not exist');
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\traits;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Trait for initialization of a status
|
||||
*
|
||||
* @package mirzaev\huesos\models\traits
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
trait status
|
||||
{
|
||||
/**
|
||||
* Initialize of a status
|
||||
*
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return ?bool Status, if they are found
|
||||
*/
|
||||
public function status(array &$errors = []): ?bool
|
||||
{
|
||||
try {
|
||||
// Read from ArangoDB and exit (success)
|
||||
return $this->document->active ?? false;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos\models\traits\yandex;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\http\enumerations\content;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Trait for "Yandex Disk"
|
||||
*
|
||||
* @package mirzaev\huesos\models\traits\yandex
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
trait disk
|
||||
{
|
||||
/**
|
||||
* Download the file from "Yandex Disk"
|
||||
*
|
||||
* @param string $uri URI of the file from "Yandex Disk"
|
||||
* @param string $destination Destination to write the file
|
||||
* @param string|int $name Name for the file
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return array|false The [destination, name, content] array of the downloaded file, if the file was downloaded
|
||||
*/
|
||||
private static function download(
|
||||
string $uri,
|
||||
string $destination,
|
||||
string|int $name,
|
||||
array &$errors = []
|
||||
): array|false {
|
||||
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
|
||||
$session = curl_init($url);
|
||||
curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_exec($session);
|
||||
$code = curl_getinfo($session, CURLINFO_RESPONSE_CODE);
|
||||
curl_close($session);
|
||||
|
||||
if ($code === 200) {
|
||||
// The file is available for download
|
||||
|
||||
// Initializing URI of the file
|
||||
$uri = json_decode(file_get_contents($url))?->href;
|
||||
|
||||
// Downloading the file
|
||||
$session = curl_init($uri);
|
||||
curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($session, CURLOPT_AUTOREFERER, true);
|
||||
curl_setopt($session, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_exec($session);
|
||||
$file = curl_exec($session);
|
||||
$code = curl_getinfo($session, CURLINFO_RESPONSE_CODE);
|
||||
preg_match('/^\w+\/\w+/', curl_getinfo($session, CURLINFO_CONTENT_TYPE), $matches);
|
||||
$type = content::from($matches[0]);
|
||||
curl_close($session);
|
||||
|
||||
if ($code === 200 && $file) {
|
||||
// The file is downloaded
|
||||
|
||||
if ($type instanceof content) {
|
||||
// Initialized content-type header
|
||||
|
||||
if (file_put_contents($destination . DIRECTORY_SEPARATOR . "$name." . $type->extension(), $file) > 0) {
|
||||
// Downloaded the file
|
||||
|
||||
// Exit (success)
|
||||
return ['destination' => $destination, 'name' => $name, 'content' => $type];
|
||||
}
|
||||
} else throw new exception("Failed to initialize content-type header");
|
||||
} else throw new exception("Failed to download the file by link: $uri");
|
||||
} 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize list of files inside the folder from "Yandex Disk"
|
||||
*
|
||||
* @param string $uri URI of the folder from "Yandex Disk"
|
||||
* @param array &$errors Registry of errors
|
||||
*
|
||||
* @return array|false JSON objects list of files in the folder
|
||||
*/
|
||||
private static function list(string $uri, array &$errors = []): array|false
|
||||
{
|
||||
try {
|
||||
if (!empty($uri)) {
|
||||
// Not empty URI
|
||||
|
||||
// Initializing URL of the file
|
||||
$url = "https://cloud-api.yandex.net/v1/disk/public/resources?public_key=$uri";
|
||||
|
||||
// Checking if the folder is available
|
||||
$session = curl_init($url);
|
||||
curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_exec($session);
|
||||
$code = curl_getinfo($session, CURLINFO_RESPONSE_CODE);
|
||||
curl_close($session);
|
||||
|
||||
if ($code === 200) {
|
||||
// The folder is available
|
||||
|
||||
// Downloading the list of files in the folder
|
||||
$files = json_decode(file_get_contents($url));
|
||||
|
||||
// Exit (success)
|
||||
return isset($files?->_embedded?->items) ? $files->_embedded->items : [$files];
|
||||
} else throw new exception("Failed to download list of files inside the folder by link: $uri");
|
||||
} else throw new exception("Empty URI");
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,348 +0,0 @@
|
|||
<?php
|
||||
|
||||
define('SETTINGS', realpath('..' . DIRECTORY_SEPARATOR . 'settings'));
|
||||
define('CDEK', require(SETTINGS . DIRECTORY_SEPARATOR . 'deliveries' . DIRECTORY_SEPARATOR . 'cdek.php'));
|
||||
|
||||
$service = new service(
|
||||
/**
|
||||
* Вставьте свой аккаунт\идентификатор для интеграции
|
||||
* Put your account for integration here
|
||||
*/
|
||||
CDEK['account'],
|
||||
|
||||
/**
|
||||
* Вставьте свой пароль для интеграции
|
||||
* Put your password for integration here
|
||||
*/
|
||||
CDEK['secret']
|
||||
);
|
||||
|
||||
$service->process($_GET, file_get_contents('php://input'));
|
||||
|
||||
class service
|
||||
{
|
||||
/**
|
||||
* @var string Auth login
|
||||
*/
|
||||
private $login;
|
||||
/**
|
||||
* @var string Auth pwd
|
||||
*/
|
||||
private $secret;
|
||||
/**
|
||||
* @var string Base Url for API 2.0 Production
|
||||
*/
|
||||
private $baseUrl;
|
||||
/**
|
||||
* @var string Auth Token
|
||||
*/
|
||||
private $authToken;
|
||||
/**
|
||||
* @var array Data From Request
|
||||
*/
|
||||
private $requestData;
|
||||
/** @var array Request metrics */
|
||||
private $metrics;
|
||||
|
||||
public function __construct($login, $secret, $baseUrl = 'https://api.cdek.ru/v2')
|
||||
{
|
||||
$this->login = $login;
|
||||
$this->secret = $secret;
|
||||
$this->baseUrl = $baseUrl;
|
||||
$this->metrics = array();
|
||||
}
|
||||
|
||||
public function process($requestData, $body)
|
||||
{
|
||||
$time = $this->startMetrics();
|
||||
|
||||
$this->requestData = array_merge($requestData, json_decode($body, true) ?: array());
|
||||
|
||||
if (!isset($this->requestData['action'])) {
|
||||
$this->sendValidationError('Action is required');
|
||||
}
|
||||
|
||||
$this->getAuthToken();
|
||||
|
||||
switch ($this->requestData['action']) {
|
||||
case 'offices':
|
||||
$this->sendResponse($this->getOffices(), $time);
|
||||
break;
|
||||
case 'calculate':
|
||||
$this->sendResponse($this->calculate(), $time);
|
||||
break;
|
||||
default:
|
||||
$this->sendValidationError('Unknown action');
|
||||
}
|
||||
}
|
||||
|
||||
private function sendValidationError($message)
|
||||
{
|
||||
$this->http_response_code(400);
|
||||
header('Content-Type: application/json');
|
||||
header('X-Service-Version: 3.11.1');
|
||||
echo json_encode(array('message' => $message));
|
||||
exit();
|
||||
}
|
||||
|
||||
private function http_response_code($code)
|
||||
{
|
||||
switch ($code) {
|
||||
case 100:
|
||||
$text = 'Continue';
|
||||
break;
|
||||
case 101:
|
||||
$text = 'Switching Protocols';
|
||||
break;
|
||||
case 200:
|
||||
$text = 'OK';
|
||||
break;
|
||||
case 201:
|
||||
$text = 'Created';
|
||||
break;
|
||||
case 202:
|
||||
$text = 'Accepted';
|
||||
break;
|
||||
case 203:
|
||||
$text = 'Non-Authoritative Information';
|
||||
break;
|
||||
case 204:
|
||||
$text = 'No Content';
|
||||
break;
|
||||
case 205:
|
||||
$text = 'Reset Content';
|
||||
break;
|
||||
case 206:
|
||||
$text = 'Partial Content';
|
||||
break;
|
||||
case 300:
|
||||
$text = 'Multiple Choices';
|
||||
break;
|
||||
case 301:
|
||||
$text = 'Moved Permanently';
|
||||
break;
|
||||
case 302:
|
||||
$text = 'Moved Temporarily';
|
||||
break;
|
||||
case 303:
|
||||
$text = 'See Other';
|
||||
break;
|
||||
case 304:
|
||||
$text = 'Not Modified';
|
||||
break;
|
||||
case 305:
|
||||
$text = 'Use Proxy';
|
||||
break;
|
||||
case 400:
|
||||
$text = 'Bad Request';
|
||||
break;
|
||||
case 401:
|
||||
$text = 'Unauthorized';
|
||||
break;
|
||||
case 402:
|
||||
$text = 'Payment Required';
|
||||
break;
|
||||
case 403:
|
||||
$text = 'Forbidden';
|
||||
break;
|
||||
case 404:
|
||||
$text = 'Not Found';
|
||||
break;
|
||||
case 405:
|
||||
$text = 'Method Not Allowed';
|
||||
break;
|
||||
case 406:
|
||||
$text = 'Not Acceptable';
|
||||
break;
|
||||
case 407:
|
||||
$text = 'Proxy Authentication Required';
|
||||
break;
|
||||
case 408:
|
||||
$text = 'Request Time-out';
|
||||
break;
|
||||
case 409:
|
||||
$text = 'Conflict';
|
||||
break;
|
||||
case 410:
|
||||
$text = 'Gone';
|
||||
break;
|
||||
case 411:
|
||||
$text = 'Length Required';
|
||||
break;
|
||||
case 412:
|
||||
$text = 'Precondition Failed';
|
||||
break;
|
||||
case 413:
|
||||
$text = 'Request Entity Too Large';
|
||||
break;
|
||||
case 414:
|
||||
$text = 'Request-URI Too Large';
|
||||
break;
|
||||
case 415:
|
||||
$text = 'Unsupported Media Type';
|
||||
break;
|
||||
case 500:
|
||||
$text = 'Internal Server Error';
|
||||
break;
|
||||
case 501:
|
||||
$text = 'Not Implemented';
|
||||
break;
|
||||
case 502:
|
||||
$text = 'Bad Gateway';
|
||||
break;
|
||||
case 503:
|
||||
$text = 'Service Unavailable';
|
||||
break;
|
||||
case 504:
|
||||
$text = 'Gateway Time-out';
|
||||
break;
|
||||
case 505:
|
||||
$text = 'HTTP Version not supported';
|
||||
break;
|
||||
default:
|
||||
exit('Unknown http status code "' . htmlentities($code) . '"');
|
||||
}
|
||||
|
||||
$protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0');
|
||||
header($protocol . ' ' . $code . ' ' . $text);
|
||||
$GLOBALS['http_response_code'] = $code;
|
||||
}
|
||||
|
||||
private function getAuthToken()
|
||||
{
|
||||
$time = $this->startMetrics();
|
||||
|
||||
$token = $this->httpRequest('oauth/token', array(
|
||||
'grant_type' => 'client_credentials',
|
||||
'client_id' => $this->login,
|
||||
'client_secret' => $this->secret,
|
||||
), true);
|
||||
|
||||
$this->endMetrics('auth', 'Server Auth Time', $time);
|
||||
|
||||
$result = json_decode($token['result'], true);
|
||||
|
||||
if (!isset($result['access_token'])) {
|
||||
throw new RuntimeException('Server not authorized to CDEK API');
|
||||
}
|
||||
|
||||
$this->authToken = $result['access_token'];
|
||||
}
|
||||
|
||||
private function startMetrics()
|
||||
{
|
||||
return function_exists('hrtime') ? hrtime(true) : microtime(true);
|
||||
}
|
||||
|
||||
private function httpRequest($method, $data, $useFormData = false, $useJson = false)
|
||||
{
|
||||
$ch = curl_init("$this->baseUrl/$method");
|
||||
|
||||
$headers = array(
|
||||
'Accept: application/json',
|
||||
'X-App-Name: widget_pvz',
|
||||
'X-App-Version: 3.11.1'
|
||||
);
|
||||
|
||||
if ($this->authToken) {
|
||||
$headers[] = "Authorization: Bearer $this->authToken";
|
||||
}
|
||||
|
||||
if ($useFormData) {
|
||||
curl_setopt_array($ch, array(
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $data,
|
||||
));
|
||||
} elseif ($useJson) {
|
||||
$headers[] = 'Content-Type: application/json';
|
||||
curl_setopt_array($ch, array(
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => json_encode($data),
|
||||
));
|
||||
} else {
|
||||
curl_setopt($ch, CURLOPT_URL, "$this->baseUrl/$method?" . http_build_query($data));
|
||||
}
|
||||
|
||||
curl_setopt_array($ch, array(
|
||||
CURLOPT_USERAGENT => 'widget/3.11.1',
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HEADER => true,
|
||||
));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$headers = substr($response, 0, $headerSize);
|
||||
$result = substr($response, $headerSize);
|
||||
|
||||
$addedHeaders = $this->getHeaderValue($headers);
|
||||
|
||||
if ($result === false) {
|
||||
throw new RuntimeException(curl_error($ch), curl_errno($ch));
|
||||
}
|
||||
|
||||
return array('result' => $result, 'addedHeaders' => $addedHeaders);
|
||||
}
|
||||
|
||||
private function getHeaderValue($headers)
|
||||
{
|
||||
$headerLines = explode("\r\n", $headers);
|
||||
return array_filter($headerLines, static function ($line) {
|
||||
return !empty($line) && stripos($line, 'X-') !== false;
|
||||
});
|
||||
}
|
||||
|
||||
private function endMetrics($metricName, $metricDescription, $start)
|
||||
{
|
||||
$this->metrics[] = array(
|
||||
'name' => $metricName,
|
||||
'description' => $metricDescription,
|
||||
'time' => round(
|
||||
function_exists('hrtime') ? (hrtime(true) - $start) / 1e+6 : (microtime(true) - $start) * 1000,
|
||||
2
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private function sendResponse($data, $start)
|
||||
{
|
||||
$this->http_response_code(200);
|
||||
header('Content-Type: application/json');
|
||||
header('X-Service-Version: 3.11.1');
|
||||
if (!empty($data['addedHeaders'])) {
|
||||
foreach ($data['addedHeaders'] as $header) {
|
||||
header($header);
|
||||
}
|
||||
}
|
||||
|
||||
$this->endMetrics('total', 'Total Time', $start);
|
||||
|
||||
if (!empty($this->metrics)) {
|
||||
header('Server-Timing: ' . array_reduce($this->metrics, function ($c, $i) {
|
||||
return $c . $i['name'] . ';desc="' . $i['description'] . '";dur=' . $i['time'] . ',';
|
||||
}, ''));
|
||||
}
|
||||
|
||||
echo $data['result'];
|
||||
|
||||
exit();
|
||||
}
|
||||
|
||||
protected function getOffices()
|
||||
{
|
||||
$time = $this->startMetrics();
|
||||
$result = $this->httpRequest('deliverypoints', $this->requestData);
|
||||
|
||||
$this->endMetrics('office', 'Offices Request', $time);
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function calculate()
|
||||
{
|
||||
$time = $this->startMetrics();
|
||||
$result = $this->httpRequest('calculator/tarifflist', $this->requestData, false, true);
|
||||
|
||||
$this->endMetrics('calc', 'Calculate Request', $time);
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\core,
|
||||
mirzaev\minimal\route;
|
||||
|
||||
// Enabling debugging
|
||||
/* 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('SETTINGS_PROJECT', require(SETTINGS . DIRECTORY_SEPARATOR . 'project.php'));
|
||||
define('INDEX', __DIR__);
|
||||
define('ROOT', INDEX . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR);
|
||||
define('THEME', 'default');
|
||||
|
||||
define('CDEK', require(SETTINGS . DIRECTORY_SEPARATOR . 'deliveries' . DIRECTORY_SEPARATOR . 'cdek.php'));
|
||||
|
||||
define('TELEGRAM_KEY', require(SETTINGS . DIRECTORY_SEPARATOR . 'telegram.php'));
|
||||
|
||||
// Initialize dependencies
|
||||
require ROOT . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
|
||||
|
||||
// Initializing core
|
||||
$core = new core(namespace: __NAMESPACE__);
|
||||
|
||||
// Initializing routes
|
||||
$core->router
|
||||
->write('/', new route('catalog', 'index', 'catalog'), 'GET')
|
||||
->write('/offer', new route('index', 'offer'), 'GET')
|
||||
->write('/cart', new route('cart', 'index', 'cart'), 'GET')
|
||||
->write('/cart/product', new route('cart', 'product', 'cart'), 'PATCH')
|
||||
->write('/cart/summary', new route('cart', 'summary', 'cart'), 'GET')
|
||||
->write('/cart/share', new route('cart', 'share', 'cart'), 'POST')
|
||||
->write('/cart/attach', new route('cart', 'attach', 'cart'), 'POST')
|
||||
->write('/order/robokassa', new route('cart', 'robokassa', 'cart'), 'GET')
|
||||
->write('/api/robokassa/result', new route('api\acquirings\robokassa', 'result'), 'POST')
|
||||
->write('/robokassa/success', new route('api\acquirings\robokassa', 'success'), 'GET')
|
||||
->write('/robokassa/fail', new route('api\acquirings\robokassa', 'fail'), 'GET')
|
||||
->write('/account/write', new route('account', 'write', 'account'), 'PATCH')
|
||||
->write('/session/write', new route('session', 'write', 'session'), 'PATCH')
|
||||
->write('/session/connect/telegram', new route('session', 'telegram', 'session'), 'PUT')
|
||||
->write('/delivery/write', new route('delivery', 'write', 'delivery'), 'PATCH')
|
||||
->write('/delivery/calculate', new route('delivery', 'calculate', 'delivery'), 'GET');
|
||||
|
||||
// Handling request
|
||||
$core->start();
|
|
@ -1,28 +0,0 @@
|
|||
// Initializing the closing element
|
||||
const closing = document.getElementById("closing");
|
||||
console.log(closing);
|
||||
// Initializing the closing iterator
|
||||
let iterator =
|
||||
parseInt(closing.style.getPropertyValue("--iterator").replaceAll("'", "")) ||
|
||||
0;
|
||||
console.log(iterator);
|
||||
// Initializing the closing inteval
|
||||
const interval = setInterval(function () {
|
||||
if (iterator-- <= 0) {
|
||||
// Deinitializing the closing inteval
|
||||
clearInterval(interval);
|
||||
|
||||
core.modules.connect("telegram").then(() => {
|
||||
// Imported the telegram module
|
||||
|
||||
// Closing the window
|
||||
core.telegram.api.close();
|
||||
});
|
||||
|
||||
// Exit (success)
|
||||
return;
|
||||
}
|
||||
|
||||
// Writing the iterator into the closing element
|
||||
closing.style.setProperty("--iterator", "'" + iterator + "'");
|
||||
}, 1000);
|
|
@ -1,403 +0,0 @@
|
|||
/**
|
||||
* @name Core
|
||||
*
|
||||
* @description
|
||||
* Core of the project
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
class core {
|
||||
// Domain
|
||||
static domain = window.location.hostname;
|
||||
|
||||
// Language
|
||||
static language = "ru";
|
||||
|
||||
// Window
|
||||
static window;
|
||||
|
||||
// Account
|
||||
static account;
|
||||
|
||||
// The "loading" element
|
||||
static loading = document.getElementById("loading");
|
||||
|
||||
// The <header> element
|
||||
static header = document.body.getElementsByTagName("header")[0];
|
||||
|
||||
// The <aside> element
|
||||
static aside = document.body.getElementsByTagName("aside")[0];
|
||||
|
||||
// The <menu> element
|
||||
static menu = document.body.getElementsByTagName("menu")[0];
|
||||
|
||||
// The <main> element
|
||||
static main = document.body.getElementsByTagName("main")[0];
|
||||
|
||||
// The <footer> element
|
||||
static footer = document.body.getElementsByTagName("footer")[0];
|
||||
|
||||
/**
|
||||
* Request
|
||||
*
|
||||
* @param {string} uri
|
||||
* @param {string} body
|
||||
* @param {string} method POST, GET...
|
||||
* @param {object} headers
|
||||
* @param {string} type Format of response (json, text...)
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async request(
|
||||
uri = "/",
|
||||
body,
|
||||
method = "GET",
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
type = "json",
|
||||
) {
|
||||
return await fetch(encodeURI(uri), { method, headers, body })
|
||||
.then((response) => type === null || response[type]());
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Modules
|
||||
*
|
||||
* @method connect(modules) Connect modules
|
||||
*
|
||||
* @return {Array} List of initialized modules
|
||||
*/
|
||||
static modules() {
|
||||
return Object.keys(this).filter((module) =>
|
||||
this[module]?.type === "module"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Buffer
|
||||
*/
|
||||
static buffer = class buffer {
|
||||
/**
|
||||
* @name Write to buffers
|
||||
*
|
||||
* @description
|
||||
* Write to buffers (interface)
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
*
|
||||
* @return {bool} Execution completed with an error?
|
||||
*/
|
||||
static write(name, value) {
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.write.damper(name, value);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.write.system(name, value);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Сгенерировать окно выбора действия
|
||||
*
|
||||
* @param {string} title Верхний колонтинул
|
||||
* @param {string} text Основное содержимое окна
|
||||
* @param {string} left Содержимое левой кнопки
|
||||
* @param {object} left_css Перечисление CSS-классов левой кнопки (массив)
|
||||
* @param {function} left_click Действие после нажатия на левую кнопку
|
||||
* @param {string} right Содержимое правой кнопки
|
||||
* @param {object} right_css Перечисление CSS-классов правой кнопки (массив)
|
||||
* @param {function} right_click Действие после нажатия на правую кнопку
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
/* static choose = core.damper(
|
||||
(
|
||||
title = "Выбор действия",
|
||||
text = "",
|
||||
left = "Да",
|
||||
left_css = ["grass"],
|
||||
left_click = () => {},
|
||||
right = "Нет",
|
||||
right_css = ["clay"],
|
||||
right_click = () => {},
|
||||
) => {
|
||||
// Инициализация оболочки всплывающего окна
|
||||
this.popup_body.wrap = document.createElement("div");
|
||||
this.popup_body.wrap.setAttribute("id", "popup");
|
||||
|
||||
// Инициализация всплывающего окна
|
||||
const popup = document.createElement("section");
|
||||
popup.classList.add("list", "small");
|
||||
|
||||
// Инициализация заголовка всплывающего окна
|
||||
const title_h3 = document.createElement("h3");
|
||||
title_h3.classList.add("unselectable");
|
||||
title_h3.innerText = title;
|
||||
|
||||
// Инициализация оболочки с основной информацией
|
||||
const main = document.createElement("section");
|
||||
main.classList.add("main");
|
||||
|
||||
// Инициализация колонки
|
||||
const column = document.createElement("div");
|
||||
column.classList.add("column");
|
||||
|
||||
// Инициализация текста
|
||||
const text_p = document.createElement("p");
|
||||
text_p.innerText = text;
|
||||
|
||||
// Инициализация строки
|
||||
const row = document.createElement("div");
|
||||
row.classList.add("row", "buttons");
|
||||
|
||||
// Инициализация левой кнопки
|
||||
const left_button = document.createElement("button");
|
||||
left_button.classList.add(...left_css);
|
||||
left_button.innerText = left;
|
||||
left_button.addEventListener("click", left_click);
|
||||
|
||||
// Инициализация правой кнопки
|
||||
const right_button = document.createElement("button");
|
||||
right_button.classList.add(...right_css);
|
||||
right_button.innerText = right;
|
||||
right_button.addEventListener("click", right_click);
|
||||
|
||||
// Инициализация окна с ошибками
|
||||
this.popup_body.errors = document.createElement("section");
|
||||
this.popup_body.errors.classList.add(
|
||||
"errors",
|
||||
"window",
|
||||
"list",
|
||||
"calculated",
|
||||
"hidden",
|
||||
);
|
||||
this.popup_body.errors.setAttribute("data-errors", true);
|
||||
|
||||
// Инициализация элемента-тела (оболочки) окна с ошибками
|
||||
const errors = document.createElement("section");
|
||||
errors.classList.add("body");
|
||||
|
||||
// Инициализация элемента-списка ошибок
|
||||
const dl = document.createElement("dl");
|
||||
|
||||
// Инициализация активного всплывающего окна
|
||||
const old = document.getElementById("popup");
|
||||
|
||||
if (old instanceof HTMLElement) {
|
||||
// Найдено активное окно
|
||||
|
||||
// Деинициализация быстрых действий по кнопкам
|
||||
document.removeEventListener("keydown", this.buttons);
|
||||
|
||||
// Сброс блокировки
|
||||
this.freeze = false;
|
||||
|
||||
// Удаление активного окна
|
||||
old.remove();
|
||||
}
|
||||
|
||||
// Запись в документ
|
||||
popup.appendChild(title_h3);
|
||||
|
||||
column.appendChild(text_p);
|
||||
|
||||
row.appendChild(left_button);
|
||||
row.appendChild(right_button);
|
||||
column.appendChild(row);
|
||||
|
||||
main.appendChild(column);
|
||||
popup.appendChild(main);
|
||||
|
||||
this.popup_body.wrap.appendChild(popup);
|
||||
document.body.appendChild(this.popup_body.wrap);
|
||||
|
||||
errors.appendChild(dl);
|
||||
this.popup_body.errors.appendChild(errors);
|
||||
this.popup_body.wrap.appendChild(this.popup_body.errors);
|
||||
|
||||
// Инициализация ширины окна с ошибками
|
||||
this.popup_body.errors.style.setProperty(
|
||||
"--calculated-width",
|
||||
popup.offsetWidth + "px",
|
||||
);
|
||||
|
||||
// Инициализация переменных для окна с ошибками (12 - это значение gap из div#popup)
|
||||
function top(errors) {
|
||||
errors.style.setProperty("transition", "0s");
|
||||
errors.style.setProperty(
|
||||
"--top",
|
||||
popup.offsetTop + popup.offsetHeight + 12 + "px",
|
||||
);
|
||||
setTimeout(() => errors.style.removeProperty("transition"), 100);
|
||||
}
|
||||
top(this.popup_body.errors);
|
||||
const resize = new ResizeObserver(() => top(this.popup_body.errors));
|
||||
resize.observe(this.popup_body.wrap);
|
||||
|
||||
// Инициализация функции закрытия всплывающего окна
|
||||
const click = () => {
|
||||
// Блокировка
|
||||
if (this.freeze) return;
|
||||
|
||||
// Удаление всплывающего окна
|
||||
this.popup_body.wrap.remove();
|
||||
|
||||
// Удаление статуса активной строки
|
||||
row.removeAttribute("data-selected");
|
||||
|
||||
// Деинициализация быстрых действий по кнопкам
|
||||
document.removeEventListener("keydown", this.buttons);
|
||||
|
||||
// Сброс блокировки
|
||||
this.freeze = false;
|
||||
};
|
||||
|
||||
// Инициализация функции добавления функции закрытия всплывающего окна
|
||||
const enable = () =>
|
||||
this.popup_body.wrap.addEventListener("click", click);
|
||||
|
||||
// Инициализация функции удаления функции закрытия всплывающего окна
|
||||
const disable = () =>
|
||||
this.popup_body.wrap.removeEventListener("click", click);
|
||||
|
||||
// Первичная активация функции удаления всплывающего окна
|
||||
enable();
|
||||
|
||||
// Добавление функции удаления всплывающего окна по событиям
|
||||
popup.addEventListener("mouseenter", disable);
|
||||
popup.addEventListener("mouseleave", enable);
|
||||
|
||||
// Добавление функции удаления всплывающего окна по кнопкам
|
||||
left_button.addEventListener("click", click);
|
||||
right_button.addEventListener("click", click);
|
||||
|
||||
// Фокусировка
|
||||
right_button.focus();
|
||||
},
|
||||
300,
|
||||
); */
|
||||
}
|
||||
|
||||
Object.assign(
|
||||
core.modules,
|
||||
{
|
||||
/**
|
||||
* @name Connect modules
|
||||
*
|
||||
* @param {Array|string} modules Names of modules or name of the module
|
||||
*
|
||||
* @return {Prommise}
|
||||
*/
|
||||
async connect(modules) {
|
||||
// Normalisation required argiments
|
||||
if (typeof modules === "string") modules = [modules];
|
||||
|
||||
if (modules instanceof Array) {
|
||||
// Received and validated required arguments
|
||||
|
||||
// Initializing the registry of loaded modules
|
||||
const loaded = [];
|
||||
|
||||
for (const module of modules) {
|
||||
// Iterating over modules
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
return loaded;
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
core.modules.connect("damper").then(() => {
|
||||
// Imported the damper module
|
||||
|
||||
Object.assign(
|
||||
core.buffer.write,
|
||||
{
|
||||
/**
|
||||
* @name Write to buffers
|
||||
*
|
||||
* @description
|
||||
* Write to buffers (damper)
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
damper: core.damper(
|
||||
(...variables) => core.buffer.write.system(...variables),
|
||||
300,
|
||||
2,
|
||||
),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* @name Write to buffers
|
||||
*
|
||||
* @description
|
||||
* Write to buffers (system)
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
Object.assign(
|
||||
core.buffer.write,
|
||||
{
|
||||
system(
|
||||
name,
|
||||
value,
|
||||
resolve = () => {},
|
||||
reject = () => {},
|
||||
) {
|
||||
try {
|
||||
core.modules.connect("session").then(() => {
|
||||
// Imported the session module
|
||||
|
||||
// Write to the session buffer
|
||||
core.session.buffer?.write(name, value);
|
||||
});
|
||||
|
||||
core.modules.connect("account").then(() => {
|
||||
// Imported the account module
|
||||
|
||||
// Write to the account buffer
|
||||
core.account.buffer?.write(name, value);
|
||||
});
|
||||
|
||||
// Exit (success)
|
||||
resolve();
|
||||
} catch (e) {
|
||||
// Exit (fail)
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
|
@ -1,272 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* @name Account
|
||||
*
|
||||
* @description
|
||||
* Implements actions with accounts
|
||||
*
|
||||
* @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 account {
|
||||
/**
|
||||
* @name Type of the program
|
||||
*/
|
||||
static type = "module";
|
||||
|
||||
// Wrap of indicator of the account
|
||||
static wrap = document.getElementById("account");
|
||||
|
||||
// Indicator of the account
|
||||
static indicator = this.wrap?.getElementsByTagName("i")[0] ?? null;
|
||||
|
||||
// Description of the account
|
||||
static description = this.wrap?.getElementsByTagName("small")[0] ?? null;
|
||||
|
||||
/**
|
||||
* Authentication
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
static authentication() {
|
||||
core.loading.removeAttribute("disabled");
|
||||
|
||||
const timer_for_response = setTimeout(() => {
|
||||
core.loading.setAttribute("disabled", true);
|
||||
}, 200);
|
||||
|
||||
core.modules.connect("telegram").then(() => {
|
||||
// Imported the telegram module
|
||||
|
||||
if (core.telegram.api.initData.length > 0) {
|
||||
core
|
||||
.request(
|
||||
"/session/connect/telegram",
|
||||
core.telegram.api.initData,
|
||||
"PUT",
|
||||
)
|
||||
.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)
|
||||
|
||||
if (json.connected === true) {
|
||||
// Deactivating the loading screen
|
||||
core.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
json.language !== null &&
|
||||
typeof json.language === "string" &&
|
||||
json.language.length === 2
|
||||
) {
|
||||
core.language = json.language;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Buffer
|
||||
*/
|
||||
static buffer = class buffer {
|
||||
/**
|
||||
* @name Write
|
||||
*
|
||||
* @description
|
||||
* Write to the account buffer (interface)
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @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?
|
||||
*/
|
||||
static write = (name, value, force = false) => {
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.write.damper(name, value, force);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.write.system(name, value, force);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
core.modules.connect("damper").then(() => {
|
||||
// Imported the damper module
|
||||
|
||||
Object.assign(
|
||||
account.buffer.write,
|
||||
{
|
||||
/**
|
||||
* @name Write (damper)
|
||||
*
|
||||
* @description
|
||||
* Write to the account buffer
|
||||
*
|
||||
* @memberof account.buffer.write
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
damper: core.damper(
|
||||
(...variables) => account.buffer.write.system(...variables),
|
||||
300,
|
||||
2,
|
||||
),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Object.assign(
|
||||
account.buffer.write,
|
||||
{
|
||||
/**
|
||||
* @name Write (system)
|
||||
*
|
||||
* @description
|
||||
* Write to the account buffer
|
||||
*
|
||||
* @memberof account.buffer.write
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
async system(
|
||||
name,
|
||||
value,
|
||||
resolve = () => {},
|
||||
reject = () => {},
|
||||
) {
|
||||
try {
|
||||
if (
|
||||
typeof name === "string" &&
|
||||
(typeof value === "string" || typeof value === "number")
|
||||
) {
|
||||
// Received and validated required arguments
|
||||
|
||||
// Sending request to the server
|
||||
return await core.request(
|
||||
"/account/write",
|
||||
`${name}=${value}`,
|
||||
"PATCH",
|
||||
)
|
||||
.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);
|
||||
}
|
||||
}
|
||||
},
|
||||
() => reject(),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// Exit (fail)
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Connecting to the core
|
||||
if (!core.account) core.account = account;
|
|
@ -1,959 +0,0 @@
|
|||
"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";
|
||||
|
||||
/**
|
||||
* @name Toggle (interface)
|
||||
*
|
||||
* @description
|
||||
* Toggle the product in the cart
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement|null} 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} Did the execution complete without errors?
|
||||
*/
|
||||
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 true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Write (interface)
|
||||
*
|
||||
* @description
|
||||
* Write the product into the cart
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement|null} 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} Did the execution complete without errors?
|
||||
*/
|
||||
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 true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Delete (interface)
|
||||
*
|
||||
* @description
|
||||
* Delete the product from the cart
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement|null} 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} Did the execution complete without errors?
|
||||
*/
|
||||
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 true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Set (interface)
|
||||
*
|
||||
* @description
|
||||
* Set amount of the product in the cart
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement|null} 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} Did the execution complete without errors?
|
||||
*/
|
||||
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 true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Product (interface)
|
||||
*
|
||||
* @description
|
||||
* Handle the product in the cart
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement|null} 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} Did the execution complete without errors?
|
||||
*/
|
||||
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 true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Summary (interface)
|
||||
*
|
||||
* @description
|
||||
* Initialize summary of products the cart
|
||||
*
|
||||
* @param {HTMLButtonElement} button Button <button>
|
||||
*
|
||||
* @return {bool} Did the execution complete without errors?
|
||||
*/
|
||||
static summary(button) {
|
||||
// Disabling button
|
||||
button?.setAttribute("disabled", true);
|
||||
|
||||
// Initializing timer of enabling the delivery company <input> (radio) element
|
||||
const enabling = setTimeout(() => {
|
||||
// Enabling button
|
||||
button?.removeAttribute("disabled");
|
||||
}, 3000);
|
||||
|
||||
/**
|
||||
* @name Resolved
|
||||
*
|
||||
* @description
|
||||
* Render the result of the resolved request
|
||||
*
|
||||
* @param {object} json
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
const resolved = (json) => {
|
||||
if (json) {
|
||||
// Received JSON-response
|
||||
|
||||
// Deinitializing timer of enabling the delivery company <input> (radio) element
|
||||
clearTimeout(enabling);
|
||||
|
||||
// Enabling button
|
||||
button?.removeAttribute("disabled");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @name Rejected
|
||||
*
|
||||
* @description
|
||||
* Render the result of the rejected request
|
||||
*
|
||||
* @param {object} json
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
const rejected = (json) => {
|
||||
if (json) {
|
||||
// Received JSON-response
|
||||
|
||||
// Deinitializing timer of enabling the delivery company <input> (radio) element
|
||||
clearTimeout(enabling);
|
||||
|
||||
// Enabling button
|
||||
button?.removeAttribute("disabled");
|
||||
}
|
||||
};
|
||||
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.summary.damper()
|
||||
.then(resolved, rejected);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.summary.system()
|
||||
.then(resolved, rejected);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Share (interface)
|
||||
*
|
||||
* @description
|
||||
* Generate sharing hash of the cart and sent ot to the chat-robot
|
||||
*
|
||||
* @param {HTMLButtonElement} button Button <button>
|
||||
*
|
||||
* @return {bool} Did the execution complete without errors?
|
||||
*/
|
||||
static async share(button) {
|
||||
return await core.modules.connect("telegram").then(
|
||||
() => {
|
||||
// Imported the telegram module
|
||||
|
||||
// Disabling button
|
||||
button?.setAttribute("disabled", true);
|
||||
|
||||
// Initializing timer of enabling the delivery company <input> (radio) element
|
||||
const enabling = setTimeout(() => {
|
||||
// Enabling button
|
||||
button?.removeAttribute("disabled");
|
||||
}, 3000);
|
||||
|
||||
/**
|
||||
* @name Resolved
|
||||
*
|
||||
* @description
|
||||
* Render the result of the resolved request
|
||||
*
|
||||
* @param {object} json
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
const resolved = (json) => {
|
||||
if (json) {
|
||||
// Received JSON-response
|
||||
|
||||
// Deinitializing timer of enabling the delivery company <input> (radio) element
|
||||
clearTimeout(enabling);
|
||||
|
||||
// Enabling button
|
||||
button?.removeAttribute("disabled");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @name Rejected
|
||||
*
|
||||
* @description
|
||||
* Render the result of the rejected request
|
||||
*
|
||||
* @param {object} json
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
const rejected = (json) => {
|
||||
if (json) {
|
||||
// Received JSON-response
|
||||
|
||||
// Deinitializing timer of enabling the delivery company <input> (radio) element
|
||||
clearTimeout(enabling);
|
||||
|
||||
// Enabling button
|
||||
button?.removeAttribute("disabled");
|
||||
}
|
||||
};
|
||||
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.share.damper()
|
||||
.then(resolved, rejected);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.share.system()
|
||||
.then(resolved, rejected);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
},
|
||||
// Exit (fail)
|
||||
() => false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
core.modules.connect("damper").then(() => {
|
||||
// Imported the damper module
|
||||
|
||||
Object.assign(
|
||||
cart.product,
|
||||
{
|
||||
/**
|
||||
* @name Product (damper)
|
||||
*
|
||||
* @description
|
||||
* Handle the product in the cart
|
||||
*
|
||||
* @memberof cart.product
|
||||
*
|
||||
* @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.system),
|
||||
300,
|
||||
5,
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
Object.assign(
|
||||
cart.summary,
|
||||
{
|
||||
/**
|
||||
* @name Summary (damper)
|
||||
*
|
||||
* @description
|
||||
* Initialize summary of products the cart
|
||||
*
|
||||
* @memberof cart.summary
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
damper: core.damper(
|
||||
(...variables) => cart.summary.system(...variables),
|
||||
300,
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
Object.assign(
|
||||
cart.share,
|
||||
{
|
||||
/**
|
||||
* @name Share (damper)
|
||||
*
|
||||
* @description
|
||||
* Generate sharing hash of the cart and sent ot to the chat-robot
|
||||
*
|
||||
* @memberof cart.share
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
damper: core.damper(
|
||||
(...variables) => cart.share.system(...variables),
|
||||
400,
|
||||
),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Object.assign(
|
||||
cart.product,
|
||||
{
|
||||
/**
|
||||
* @name Product (system)
|
||||
*
|
||||
* @description
|
||||
* Handle the product in the cart
|
||||
*
|
||||
* @memberof cart.product
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement|null} 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 (product instanceof HTMLElement) {
|
||||
// Validated required arguments
|
||||
|
||||
// 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;
|
||||
|
||||
if (
|
||||
(type === "toggle" &&
|
||||
amount === null ||
|
||||
typeof amount === "undefined") ||
|
||||
(type === "set" &&
|
||||
amount === 0 ||
|
||||
amount === 100) ||
|
||||
typeof amount === "number" &&
|
||||
amount > 0 &&
|
||||
amount < 100
|
||||
) {
|
||||
// Validated 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,
|
||||
"PATCH",
|
||||
)
|
||||
.then(
|
||||
(json) => {
|
||||
if (json) {
|
||||
// Received a JSON-response
|
||||
|
||||
if (
|
||||
json.errors !== null &&
|
||||
typeof json.errors === "object" &&
|
||||
json.errors.length > 0
|
||||
) {
|
||||
// Fail (received errors)
|
||||
|
||||
core.modules.connect("telegram").then(() => {
|
||||
// Imported the telegram module
|
||||
|
||||
core.telegram.api.HapticFeedback
|
||||
.notificationOccurred("error");
|
||||
});
|
||||
|
||||
// Exit (fail)
|
||||
reject(json);
|
||||
} else {
|
||||
// Success (not received errors)
|
||||
|
||||
core.modules.connect("telegram").then(() => {
|
||||
// Imported the telegram module
|
||||
|
||||
core.telegram.api.HapticFeedback
|
||||
.notificationOccurred("success");
|
||||
});
|
||||
|
||||
core.modules.connect("delivery").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Calculating delivery
|
||||
core.delivery.calculate();
|
||||
},
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
Object.assign(
|
||||
cart.summary,
|
||||
{
|
||||
/**
|
||||
* @name Summary (system)
|
||||
*
|
||||
* @description
|
||||
* Initialize summary of products the cart
|
||||
*
|
||||
* @memberof cart.summary
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
async system(
|
||||
resolve = () => {},
|
||||
reject = () => {},
|
||||
) {
|
||||
try {
|
||||
// Request
|
||||
return await core.request("/cart/summary", undefined, "GET")
|
||||
.then((json) => {
|
||||
if (json) {
|
||||
// Received a JSON-response
|
||||
|
||||
if (
|
||||
json.errors !== null &&
|
||||
typeof json.errors === "object" &&
|
||||
json.errors.length > 0
|
||||
) {
|
||||
// Fail (received errors)
|
||||
|
||||
core.modules.connect("telegram").then(() => {
|
||||
// Imported the telegram module
|
||||
|
||||
core.telegram.api.HapticFeedback.notificationOccurred(
|
||||
"error",
|
||||
);
|
||||
});
|
||||
|
||||
// Exit (fail)
|
||||
reject(json);
|
||||
} else {
|
||||
// Success (not received errors)
|
||||
|
||||
if (json.amount > 0) {
|
||||
// The cart has products
|
||||
|
||||
// Writing the CSS variable of the document element
|
||||
document.documentElement.style.setProperty(
|
||||
"--cart-amount",
|
||||
'"' + json.amount + '"',
|
||||
);
|
||||
} else {
|
||||
// The cart has no products
|
||||
|
||||
// Writing the CSS variable of the document element
|
||||
document.documentElement.style.setProperty(
|
||||
"--cart-amount",
|
||||
"unset",
|
||||
);
|
||||
}
|
||||
|
||||
// Initializing the summary amount <span> element
|
||||
const amount = document.getElementById("amount");
|
||||
|
||||
if (amount instanceof HTMLElement) {
|
||||
// Initialized the summary amount element
|
||||
|
||||
// Writing summary 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
|
||||
|
||||
core.modules.connect("telegram").then(() => {
|
||||
// Imported the telegram module
|
||||
|
||||
core.telegram.api.HapticFeedback.notificationOccurred(
|
||||
"success",
|
||||
);
|
||||
});
|
||||
|
||||
// Writing summary cost into the summary cost element
|
||||
cost.innerText = json.cost;
|
||||
}
|
||||
|
||||
// Dispatching event
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("core.cart.summary.received", {
|
||||
detail: {
|
||||
json,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
resolve(json);
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
reject(json);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
// Exit (fail)
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
Object.assign(
|
||||
cart.share,
|
||||
{
|
||||
/**
|
||||
* @name Share (system)
|
||||
*
|
||||
* @description
|
||||
* Generate sharing hash of the cart and sent ot to the chat-robot
|
||||
*
|
||||
* @memberof cart.share
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
async system(
|
||||
resolve = () => {},
|
||||
reject = () => {},
|
||||
) {
|
||||
try {
|
||||
// Request
|
||||
return await core.request("/cart/share", undefined, "POST")
|
||||
.then(async (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)
|
||||
|
||||
if (json.share) {
|
||||
// Received sharing hash
|
||||
|
||||
// Sending the request to the chat-robot
|
||||
const sended = core.telegram.api.sendData(
|
||||
JSON.stringify({
|
||||
type: "cart_share",
|
||||
hash: json.share,
|
||||
}),
|
||||
);
|
||||
|
||||
if (sended === undefined) {
|
||||
// Failed to send the request
|
||||
|
||||
if (core.account.identifier > 0) {
|
||||
// Initialized the account identifier
|
||||
|
||||
// Request
|
||||
return await core.request(
|
||||
"/cart/attach",
|
||||
'share=' + json.share,
|
||||
"POST",
|
||||
).then((json) => {
|
||||
if (json) {
|
||||
// Received a JSON-response
|
||||
|
||||
if (json.success) {
|
||||
// Received the success status
|
||||
|
||||
core.modules.connect("telegram").then(() => {
|
||||
// Imported the telegram module
|
||||
|
||||
// Closing the Telegram Mini App
|
||||
core.telegram.api.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* if (json.robokassa) {
|
||||
// Received data for the Robokassa acquiring
|
||||
|
||||
// Initializing iframe
|
||||
Robokassa.StartPayment({
|
||||
MerchantLogin: json.robokassa.identifier,
|
||||
OutSum: json.robokassa.cost,
|
||||
InvId: json.robokassa.cart,
|
||||
Description: json.robokassa.description,
|
||||
Culture: json.robokassa.language,
|
||||
Encoding: "utf-8",
|
||||
Settings: JSON.stringify({
|
||||
PaymentMethods: ["BankCard", "SBP"],
|
||||
Mode: "modal",
|
||||
}),
|
||||
SignatureValue: json.robokassa.hash,
|
||||
});
|
||||
} */
|
||||
|
||||
// Exit (success)
|
||||
resolve(json);
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
reject(json);
|
||||
}
|
||||
});
|
||||
} 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
|
@ -1,305 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* @name Connection
|
||||
*
|
||||
* @description
|
||||
* Implements actions with websocket connection
|
||||
*
|
||||
* @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 connection {
|
||||
/**
|
||||
* @name Type of the program
|
||||
*/
|
||||
static type = "module";
|
||||
|
||||
// Wrap of indicator of the connection
|
||||
static wrap = document.getElementById("connection");
|
||||
|
||||
// Indicator of the connection
|
||||
static indicator = this.wrap?.getElementsByTagName("i")[0];
|
||||
|
||||
// Description of the connection
|
||||
static description = this.wrap?.getElementsByTagName("small")[0];
|
||||
|
||||
// Statuc of the connection
|
||||
static connected = false;
|
||||
|
||||
// Duration of the disconnected status
|
||||
static timeout = 0;
|
||||
|
||||
// Instance of the time counter in disconnected status
|
||||
static counter;
|
||||
|
||||
// Socket address (xn--e1ajlli это сокет)
|
||||
static socket = "wss://arming.dev.mirzaev.sexy:9502";
|
||||
|
||||
// Instance of connection to the socket
|
||||
static session;
|
||||
|
||||
// Iterval for reconnect
|
||||
static interval;
|
||||
|
||||
// Attempts to connect (when connection.readyState === 0)
|
||||
static attempts = 0;
|
||||
|
||||
// Interval for block
|
||||
static block;
|
||||
|
||||
/**
|
||||
* Initialize status of the connection to socket
|
||||
*
|
||||
* @param {bool} connected Connected?
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
static status(connected = false) {
|
||||
if (this.indicator instanceof HTMLElement) {
|
||||
// Initialized the indicator
|
||||
|
||||
this.connected = connected;
|
||||
|
||||
if (this.connected) {
|
||||
// Connected
|
||||
|
||||
this.wrap.setAttribute("title", "Connected");
|
||||
|
||||
this.indicator.classList.remove("disconnected");
|
||||
this.indicator.classList.add("connected");
|
||||
|
||||
clearInterval(this.counter);
|
||||
this.description.innerText = "";
|
||||
this.counter = undefined;
|
||||
this.timeout = 0;
|
||||
} else {
|
||||
// Disconnected
|
||||
|
||||
this.wrap.setAttribute("title", "Disconnected");
|
||||
|
||||
this.indicator.classList.remove("connected");
|
||||
this.indicator.classList.add("disconnected");
|
||||
|
||||
if (typeof this.counter === "undefined") {
|
||||
this.counter = setInterval(() => {
|
||||
this.timeout += 0.01;
|
||||
this.description.innerText = this.timeout.toFixed(2);
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the socket
|
||||
*
|
||||
* @param {bool|number} interval Connection check interval (ms)
|
||||
* @param {function} preprocessing Will be executed every cycle
|
||||
* @param {function} onmessage New message
|
||||
* @param {function} onopen Connection opened
|
||||
* @param {function} onclose Connection closed
|
||||
* @param {function} onerror An error has occurred
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
static connect(
|
||||
interval = false,
|
||||
preprocessing,
|
||||
onmessage,
|
||||
onopen,
|
||||
onclose,
|
||||
onerror,
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
if (typeof interval === "number" && interval > 0) {
|
||||
// Connect with automatic reconnect
|
||||
|
||||
if (typeof this.interval === "undefined") {
|
||||
this.interval = setInterval(() => {
|
||||
preprocessing();
|
||||
|
||||
if (
|
||||
!(this.session instanceof WebSocket) ||
|
||||
(this.session.readyState === 3 ||
|
||||
this.session.readyState === 4) ||
|
||||
(this.session.readyState === 0 &&
|
||||
++this.attempts > 10)
|
||||
) {
|
||||
this.attempts = 0;
|
||||
|
||||
if (this.session instanceof WebSocket) {
|
||||
this.session.close();
|
||||
}
|
||||
|
||||
this.session = new WebSocket(this.socket);
|
||||
this.session.addEventListener("message", (e) => {
|
||||
try {
|
||||
const json = JSON.parse(e.data);
|
||||
|
||||
if (json.type === "registration") {
|
||||
// Подключение сокета к сессии
|
||||
|
||||
fetch("/socket/registration", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: `key=${json.key}`,
|
||||
});
|
||||
}
|
||||
} catch (_e) {}
|
||||
});
|
||||
this.session.addEventListener("message", onmessage);
|
||||
this.session.addEventListener("open", onopen);
|
||||
this.session.addEventListener("close", onclose);
|
||||
this.session.addEventListener("error", onerror);
|
||||
|
||||
resolve(this.session);
|
||||
} else resolve(this.session);
|
||||
}, interval);
|
||||
}
|
||||
} else {
|
||||
// Connect without reconnecting
|
||||
|
||||
if (
|
||||
!(this.session instanceof WebSocket) ||
|
||||
(this.session.readyState === 3 ||
|
||||
this.session.readyState === 4)
|
||||
) {
|
||||
if (this.session instanceof WebSocket) {
|
||||
this.session.close();
|
||||
}
|
||||
|
||||
this.session = new WebSocket(this.socket);
|
||||
this.session.addEventListener("message", onmessage);
|
||||
this.session.addEventListener("open", onopen);
|
||||
this.session.addEventListener("close", onclose);
|
||||
this.session.addEventListener("error", onerror);
|
||||
|
||||
resolve(this.session);
|
||||
} else resolve(this.session);
|
||||
}
|
||||
} catch (_e) {}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Core is connected to the socket?
|
||||
*
|
||||
* @return {bool}
|
||||
*/
|
||||
static connected() {
|
||||
return this.session instanceof WebSocket &&
|
||||
this.session.readyState === 1;
|
||||
}
|
||||
}
|
||||
|
||||
connection.connect(
|
||||
3000,
|
||||
() =>
|
||||
connection.status(
|
||||
connection.session instanceof WebSocket &&
|
||||
connection.session.readyState === 1,
|
||||
),
|
||||
(e) => {
|
||||
try {
|
||||
const json = JSON.parse(e.data);
|
||||
|
||||
if (json.target === "task") {
|
||||
// Заявка
|
||||
|
||||
// Инициализация строки
|
||||
const row = document.getElementById(json._key);
|
||||
|
||||
if (row instanceof HTMLElement) {
|
||||
// Инициализирована строка
|
||||
|
||||
if (json.type === "blocked") {
|
||||
// Заблокирована заявка
|
||||
|
||||
// Запись статуса: "заблокирована"
|
||||
row.setAttribute("data-blocked", json.account._key);
|
||||
row.setAttribute(
|
||||
"title",
|
||||
"Редактирует: " + json.account.name,
|
||||
);
|
||||
|
||||
// Удалить блокировку (60000 === 1 минута) (в базе данных стоит expires 1 минута тоже)
|
||||
setTimeout(() => {
|
||||
// Удаление статуса: "заблокирована"
|
||||
row.removeAttribute("data-blocked");
|
||||
row.removeAttribute("title");
|
||||
|
||||
// Обновление строки
|
||||
tasks.row(row);
|
||||
}, 60000);
|
||||
} else if (json.type === "unblocked") {
|
||||
// Разблокирована заявка
|
||||
|
||||
// Удаление статуса: "заблокирована"
|
||||
row.removeAttribute("data-blocked");
|
||||
row.removeAttribute("title");
|
||||
|
||||
// Обновление строки
|
||||
tasks.row(row);
|
||||
} else if (json.type === "updated") {
|
||||
// Обновлена заявка
|
||||
|
||||
// Обновление строки
|
||||
tasks.row(row);
|
||||
} else if (json.type === "deleted") {
|
||||
// Удалена заявка
|
||||
|
||||
// Удаление строки
|
||||
row.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_e) {}
|
||||
// Инициализация идентифиатора
|
||||
//const id = row.getAttribute("id");
|
||||
|
||||
// Инициализация количества непрочитанных сообщений
|
||||
//const messages = row.lastElementChild.innerText;
|
||||
|
||||
// Инициализация статуса активной строки
|
||||
//const selected = row.getAttribute("data-selected");
|
||||
|
||||
// Реинициализация строки
|
||||
//row.outerHTML = data.rows;
|
||||
|
||||
// Реинициализация перезаписанной строки
|
||||
//row = document.getElementById(id);
|
||||
|
||||
// Копирование статуса активной строки
|
||||
//if (
|
||||
// typeof selected === "string" &&
|
||||
// selected === "true" &&
|
||||
// document.body.contains(document.getElementById("popup"))
|
||||
//) {
|
||||
// row.setAttribute("data-selected", "true");
|
||||
//}
|
||||
},
|
||||
(e) => {
|
||||
//connection.status(
|
||||
// connection instanceof WebSocket &&
|
||||
// connection.readyState === 1,
|
||||
//)
|
||||
//console.log("Connected to WebSocket!");
|
||||
},
|
||||
(e) => {
|
||||
//connection.status(
|
||||
// connection instanceof WebSocket &&
|
||||
// connection.readyState === 1,
|
||||
//)
|
||||
//console.log("Connection closed");
|
||||
},
|
||||
(e) => {
|
||||
//console.log("Error happens");
|
||||
},
|
||||
);
|
||||
|
||||
// Connecting to the core
|
||||
if (!core.connection) core.connection = connection;
|
|
@ -1,82 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* @name Damper
|
||||
*
|
||||
* @description
|
||||
* Execute multiple "function" calls in a "timeout" amount of time just once
|
||||
*
|
||||
* @param {function} function Function to execute after damping
|
||||
* @param {number} timeout Timer in milliseconds (ms)
|
||||
* @param {number} force Argument number storing the status of enforcement execution (see @example)
|
||||
*
|
||||
* @return {Promise}
|
||||
*
|
||||
* @example
|
||||
* a = damper(
|
||||
* async (
|
||||
* a, // 0
|
||||
* b, // 1
|
||||
* c, // 2
|
||||
* force = false, // 3
|
||||
* d, // 4
|
||||
* resolve,
|
||||
* reject
|
||||
* ) => {
|
||||
* // Body of the function
|
||||
*
|
||||
* resolve();
|
||||
* },
|
||||
* 500,
|
||||
* 3, // 3 -> "force" argument
|
||||
* );
|
||||
*
|
||||
* a('for a', 'for b', 'for c', true, 'for d'); // Force execute is enabled
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
export default function damper(func, timeout = 300, force) {
|
||||
// Declaring of the timer for executing the function
|
||||
let timer;
|
||||
|
||||
return ((...args) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Deinitializing of the timer
|
||||
clearTimeout(timer);
|
||||
|
||||
if (typeof force === "number" && args[force]) {
|
||||
// Requested execution with ignoring the timer
|
||||
|
||||
// Deleting the force argument
|
||||
if (typeof force === "number") args = [
|
||||
...args.splice(0, force),
|
||||
...args.splice(force + 1)
|
||||
];
|
||||
|
||||
// Writing promise handlers into the arguments variable
|
||||
args.push(resolve, reject);
|
||||
|
||||
// Executing the function
|
||||
func.apply(this, args);
|
||||
} else {
|
||||
// Normal execution
|
||||
|
||||
// Deleting the force argument
|
||||
if (typeof force === "number") args = [
|
||||
...args.splice(0, force),
|
||||
...args.splice(force + 1)
|
||||
];
|
||||
|
||||
// Writing promise handlers into the arguments variable
|
||||
args.push(resolve, reject);
|
||||
|
||||
// Resetting the timer and executing the function when the timer expires
|
||||
timer = setTimeout(() => func.apply(this, args), timeout);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Connecting to the core
|
||||
if (!core.damper) core.damper = damper;
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,280 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* @name Loader
|
||||
*
|
||||
* @description
|
||||
* Implements actions with loading and rendering content
|
||||
*
|
||||
* @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 loader {
|
||||
/**
|
||||
* @name Type of the program
|
||||
*/
|
||||
static type = "module";
|
||||
|
||||
/**
|
||||
* @name Actial URI
|
||||
*/
|
||||
static uri;
|
||||
|
||||
/**
|
||||
* @name Load
|
||||
*
|
||||
* @param {string} uri
|
||||
* @param {string} body
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async load(uri = "/", body, back = false) {
|
||||
if (typeof uri === "string") {
|
||||
// Received and validated uri
|
||||
|
||||
return await core
|
||||
.request(
|
||||
uri,
|
||||
body,
|
||||
"GET",
|
||||
{
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "application/json",
|
||||
},
|
||||
"json",
|
||||
)
|
||||
.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)
|
||||
|
||||
if (back) {
|
||||
// Requested to go back
|
||||
|
||||
// Writing actual URI
|
||||
this.uri = history.state?.previous;
|
||||
|
||||
// Deletimg from the browser history
|
||||
history.back();
|
||||
} else {
|
||||
// Requested to go forward
|
||||
|
||||
// Writing to the browser history
|
||||
history.pushState(
|
||||
{ previous: this.uri },
|
||||
json.title ?? uri,
|
||||
uri,
|
||||
);
|
||||
|
||||
// Writing actual URI
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* The <title>
|
||||
*
|
||||
* The title of the page
|
||||
*/
|
||||
if (
|
||||
typeof json.title === "string" &&
|
||||
json.title.length > 0
|
||||
) {
|
||||
// Received text for the <title> of the page
|
||||
|
||||
// Search for the <title> element (document.title)
|
||||
const title = document.getElementsByTagName("title")[0];
|
||||
|
||||
if (title instanceof HTMLElement) {
|
||||
// Found the <title> element
|
||||
|
||||
// Writing into the <title> element
|
||||
title.innerText = json.title;
|
||||
} else {
|
||||
// Not found the <title> element
|
||||
|
||||
// Initialize the <title> element
|
||||
const title = document.createElement("title");
|
||||
|
||||
// Inititalize the <head> element (document.head)
|
||||
const head = document.getElementsByTagName("head")[0];
|
||||
|
||||
if (head instanceof HTMLElement) {
|
||||
// Found the <head> element
|
||||
|
||||
// Writing the <title> element into the <head> element
|
||||
head.appendChild(title);
|
||||
}
|
||||
|
||||
// Writing title into the <title> element
|
||||
title.innerText = json.title;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The <header> element
|
||||
*/
|
||||
if (
|
||||
typeof json.header === "string" &&
|
||||
json.header.length > 0
|
||||
) {
|
||||
// Received and validated the header HTML-code
|
||||
|
||||
if (core.header instanceof HTMLElement) {
|
||||
// Found the <header> element
|
||||
|
||||
// Writing into the <header> element
|
||||
core.header.outerHTML = json.header;
|
||||
|
||||
// Reinitializing the parameter with the <header> element
|
||||
core.header = document.getElementsByTagName("header")[0];
|
||||
} else {
|
||||
// Not found the <header> element
|
||||
|
||||
// Initialize the <header> element
|
||||
core.header = document.createElement("header");
|
||||
|
||||
// Inititalize the <body> element (document.body)
|
||||
const body = document.getElementsByTagName("body")[0];
|
||||
|
||||
if (body instanceof HTMLElement) {
|
||||
// Found the <body> element
|
||||
|
||||
if (core.main instanceof HTMLElement) {
|
||||
// Found the <main> element
|
||||
|
||||
// Writing the <header> element before the <main> element
|
||||
body.insertBefore(core.header, core.main);
|
||||
} else if (core.footer instanceof HTMLElement) {
|
||||
// Fount the <footer> element
|
||||
|
||||
// Writing the <header> element before the <footer> element
|
||||
body.insertBefore(core.header, core.footer);
|
||||
} else {
|
||||
// Not found the <main> element and the <footer> element
|
||||
|
||||
// Search for the last <section> element inside the <body> element
|
||||
const section = document.body.querySelector(
|
||||
"body > section:last-of-type",
|
||||
);
|
||||
|
||||
if (section instanceof HTMLElement) {
|
||||
// Found the last <section> element inside the <body> element
|
||||
|
||||
// Writing the <header> element after the last <section> element inside the <body> element
|
||||
body.insertBefore(
|
||||
core.header,
|
||||
section.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not found section elements <section> inside the <body> element
|
||||
|
||||
// Writing the <header> element into the <body> element
|
||||
body.appendChild(core.header);
|
||||
}
|
||||
}
|
||||
|
||||
// Writing into the <header> element
|
||||
core.header.outerHTML = json.header;
|
||||
|
||||
// Reinitializing the parameter with the <header> element
|
||||
core.header = document.getElementsByTagName("header")[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The <main> element
|
||||
*
|
||||
* The main content of the page
|
||||
*/
|
||||
if (typeof json.main === "string" && json.main.length > 0) {
|
||||
// Received and validated the <main> HTML-code
|
||||
|
||||
if (core.main instanceof HTMLElement) {
|
||||
// Found the <main> element
|
||||
|
||||
// Writing into the <main> element
|
||||
core.main.outerHTML = json.main;
|
||||
|
||||
// Reinitializing the parameter with the <main> element
|
||||
core.main = document.getElementsByTagName("main")[0];
|
||||
} else {
|
||||
// Not found the <main> element
|
||||
|
||||
// Initialize the <main> element
|
||||
core.main = document.createElement("main");
|
||||
|
||||
// Inititalize the <body> element (document.body)
|
||||
const body = document.getElementsByTagName("body")[0];
|
||||
|
||||
if (body instanceof HTMLElement) {
|
||||
// Found the <body> element
|
||||
|
||||
if (core.header instanceof HTMLElement) {
|
||||
// Found the <header> element
|
||||
|
||||
// Writing the <main> element after the <header> element
|
||||
body.insertBefore(
|
||||
core.main,
|
||||
core.header.nextElementSibling,
|
||||
);
|
||||
} else if (core.footer instanceof HTMLElement) {
|
||||
// Fount the <footer> element
|
||||
|
||||
// Writing the <main> element before the <footer> element
|
||||
body.insertBefore(core.main, core.footer);
|
||||
} else {
|
||||
// Not found the <header> element and the <footer> element
|
||||
|
||||
// Search for the last <section> element inside the <body> element
|
||||
const section = document.body.querySelector(
|
||||
"body > section:last-of-type",
|
||||
);
|
||||
|
||||
if (section instanceof HTMLElement) {
|
||||
// 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,
|
||||
);
|
||||
} else {
|
||||
// Not found section elements <section> inside the <body> element
|
||||
|
||||
// Writing the <main> element into the <body> element
|
||||
body.appendChild(core.main);
|
||||
}
|
||||
}
|
||||
|
||||
// Writing into the <main> element
|
||||
core.main.outerHTML = json.main;
|
||||
|
||||
// Reinitializing the parameter with the <main> element
|
||||
core.main = document.getElementsByTagName("main")[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
return json;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connecting to the core
|
||||
if (!core.loader) core.loader = loader;
|
|
@ -1,149 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* @name Session
|
||||
*
|
||||
* @description
|
||||
* Implements actions with sessions
|
||||
*
|
||||
* @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 session {
|
||||
/**
|
||||
* @name Type of the program
|
||||
*/
|
||||
static type = "module";
|
||||
|
||||
/**
|
||||
* @name Buffer
|
||||
*/
|
||||
static buffer = class buffer {
|
||||
/**
|
||||
* @name Write
|
||||
*
|
||||
* @description
|
||||
* Write to the session buffer (interface)
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @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?
|
||||
*/
|
||||
static write = (name, value, force = false) => {
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.write.damper(name, value, force);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.write.system(name, value, force);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
core.modules.connect("damper").then(() => {
|
||||
// Imported the damper module
|
||||
|
||||
Object.assign(
|
||||
session.buffer.write,
|
||||
{
|
||||
/**
|
||||
* @name Write (damper)
|
||||
*
|
||||
* @description
|
||||
* Write to the session buffer
|
||||
*
|
||||
* @memberof session.buffer.write
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
damper: core.damper(
|
||||
(...variables) => session.buffer.write.system(...variables),
|
||||
300,
|
||||
2,
|
||||
),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Object.assign(
|
||||
session.buffer.write,
|
||||
{
|
||||
/**
|
||||
* @name Write (system)
|
||||
*
|
||||
* @description
|
||||
* Write to the session buffer
|
||||
*
|
||||
* @memberof session.buffer.write
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
async system(
|
||||
name,
|
||||
value,
|
||||
resolve = () => {},
|
||||
reject = () => {},
|
||||
) {
|
||||
if (
|
||||
typeof name === "string" &&
|
||||
(typeof value === "string" || typeof value === "number")
|
||||
) {
|
||||
// Received and validated required arguments
|
||||
|
||||
// Sending request to the server
|
||||
return await core.request(
|
||||
"/session/write",
|
||||
`${name}=${value}`,
|
||||
"PATCH",
|
||||
)
|
||||
.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);
|
||||
}
|
||||
}
|
||||
},
|
||||
() => reject(),
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Connecting to the core
|
||||
// if (!core.session) core.session = session;
|
|
@ -1,32 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* @name Telegram
|
||||
*
|
||||
* @description
|
||||
* Implements actions with data of the telegram account
|
||||
*
|
||||
* @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 telegram {
|
||||
/**
|
||||
* @name Type of the program
|
||||
*/
|
||||
static type = "module";
|
||||
|
||||
/**
|
||||
* Telegram "WebApp" API
|
||||
*
|
||||
* @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
|
||||
if (!core.telegram) core.telegram = telegram;
|
|
@ -1,61 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
core.modules.connect(["telegram"])
|
||||
.then(() => {
|
||||
// Imported the telegram module
|
||||
|
||||
// Expanding the "Web App" window
|
||||
core.telegram.api.expand();
|
||||
|
||||
// Writing settings for the "Web App" window
|
||||
core.telegram.api.enableVerticalSwipes();
|
||||
core.telegram.api.disableClosingConfirmation();
|
||||
core.telegram.api.setHeaderColor(core.telegram.api.themeParams.header_bg_color);
|
||||
core.telegram.api.setBackgroundColor(core.telegram.api.themeParams.secondary_bg_color);
|
||||
core.telegram.api.setBottomBarColor(core.telegram.api.themeParams.bottom_bar_bg_color);
|
||||
|
||||
// Writing settings for the "Back Button" of the "Web App" window
|
||||
core.telegram.api.BackButton.hide();
|
||||
|
||||
if (core.telegram.api.themeParams.section_bg_color === core.telegram.api.themeParams.bg_color) {
|
||||
// The sections color matches the background color
|
||||
|
||||
// Replacing the sections color with the secondary background color
|
||||
document.documentElement.style.setProperty('--tg-theme-section-bg-color', 'var(--tg-theme-secondary-bg-color)');
|
||||
}
|
||||
|
||||
if (core.telegram.back.length > 0) {
|
||||
// Initialized BackButton events
|
||||
|
||||
// Initializing the "Back Button" of the "Web App" window
|
||||
core.telegram.api.BackButton.show();
|
||||
|
||||
for (const event of core.telegram.back) {
|
||||
// Iterating over BackButton events
|
||||
|
||||
// Initializing the BackButton event listener
|
||||
core.telegram.api.BackButton.onClick(event);
|
||||
}
|
||||
}
|
||||
|
||||
core.modules.connect(["session", "account"])
|
||||
.then(() => {
|
||||
//
|
||||
|
||||
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();
|
||||
});
|
|
@ -1,239 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\controllers\core as controller,
|
||||
mirzaev\huesos\models\core as model,
|
||||
mirzaev\huesos\models\cart,
|
||||
mirzaev\huesos\models\telegram;
|
||||
|
||||
// Framework for Telegram
|
||||
use Zanzara\Zanzara as zanzara,
|
||||
Zanzara\Context as context,
|
||||
Zanzara\Config as config;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\document;
|
||||
|
||||
ini_set('error_reporting', E_ALL ^ E_DEPRECATED);
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
|
||||
// Версия робота
|
||||
define('ROBOT_VERSION', '1.0.0');
|
||||
|
||||
// Путь до настроек
|
||||
define('SETTINGS', __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'settings');
|
||||
|
||||
// Путь до хранилища
|
||||
define('STORAGE', __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'storage');
|
||||
|
||||
// Файл в формате xlsx с примером excel-документа для импорта каталога
|
||||
define('CATALOG_EXAMPLE', STORAGE . DIRECTORY_SEPARATOR . 'example.xlsx');
|
||||
|
||||
// Файл
|
||||
define('GREETING_VIDEO', STORAGE . DIRECTORY_SEPARATOR . 'greeting.mp4');
|
||||
|
||||
// Файл в формате xlsx для импорта каталога
|
||||
define('CATALOG_IMPORT', STORAGE . DIRECTORY_SEPARATOR . 'import.xlsx');
|
||||
|
||||
/**
|
||||
* Ключ чат-робота Telegram
|
||||
* @deprecated
|
||||
*/
|
||||
define('KEY', require(SETTINGS . DIRECTORY_SEPARATOR . 'telegram.php'));
|
||||
define('TELEGRAM_KEY', require(SETTINGS . DIRECTORY_SEPARATOR . 'telegram.php'));
|
||||
|
||||
// Initialize dependencies
|
||||
require __DIR__ . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. 'vendor' . DIRECTORY_SEPARATOR
|
||||
. 'autoload.php';
|
||||
|
||||
// Инициализация ядра контроллеров MINIMAL
|
||||
/* new controller(new core, false); */
|
||||
|
||||
// Инициализация ядра моделей MINIMAL
|
||||
new model(true);
|
||||
|
||||
$config = new config();
|
||||
$config->setParseMode(config::PARSE_MODE_MARKDOWN);
|
||||
$config->useReactFileSystem(true);
|
||||
|
||||
$robot = new zanzara(TELEGRAM_KEY, $config);
|
||||
|
||||
$robot->onUpdate(function (context $context): void {
|
||||
// Initializing the message
|
||||
$message = $context->getMessage();
|
||||
|
||||
// Initializing the "web app" data
|
||||
$app = $message?->getWebAppData();
|
||||
|
||||
if (!empty($app)) {
|
||||
// Initialized the "web app" data
|
||||
|
||||
// Initializing request from "web app" data
|
||||
$request = json_decode($app->getData(), false, 10);
|
||||
|
||||
if ($request->type === 'cart_share') {
|
||||
// Cart attaching
|
||||
|
||||
// Attaching cart to the Telegram account
|
||||
telegram::cart_attach($context, $request->hash);
|
||||
}
|
||||
} else {
|
||||
// Not initialized the "web app" data
|
||||
|
||||
// Initializing account
|
||||
$account = $context->get('account');
|
||||
|
||||
if ($account) {
|
||||
// Initialized the account
|
||||
|
||||
if (!empty($message)) {
|
||||
// Initialized the message
|
||||
|
||||
// Initializing the contact data
|
||||
$contact = $message?->getContact();
|
||||
|
||||
if (!empty($contact)) {
|
||||
// Initialized the contact data
|
||||
|
||||
// Sanitizing received SIM-number (only numbers)
|
||||
$sanitized = preg_replace('/[^\d]/', '', $contact->getPhoneNumber());
|
||||
|
||||
if (!empty($sanitized)) {
|
||||
// Sanitized receiver SIM-number
|
||||
|
||||
// Writing receiver SIM-number into the account
|
||||
$account->receiver = ['sim' => (int) $sanitized] + ($account->receiver ?? []);
|
||||
|
||||
// Deabstracting the language parameter
|
||||
$account->language = $account->language->name;
|
||||
|
||||
if (document::update($account->__document())) {
|
||||
// Writed the account instance into the ArangoDB document
|
||||
|
||||
/*
|
||||
// Sending the message
|
||||
$context->sendMessage(
|
||||
<<<TXT
|
||||
✅ *SIM\-номер зарегистрирован:* $sanitized
|
||||
TXT,
|
||||
[
|
||||
'reply_markup' => [
|
||||
'remove_keyboard' => true
|
||||
]
|
||||
]
|
||||
)->then(function ($message) use ($context) {
|
||||
// Sended message
|
||||
|
||||
// Sending the account parameters menu
|
||||
telegram::account_parameters($context);
|
||||
}); */
|
||||
|
||||
// Sending the message
|
||||
$context->sendMessage(
|
||||
<<<TXT
|
||||
✅ *Номер телефона зарегистрирован:* $sanitized
|
||||
TXT,
|
||||
[
|
||||
'reply_markup' => [
|
||||
'remove_keyboard' => true
|
||||
]
|
||||
]
|
||||
)->then(function ($message) use ($context) {
|
||||
// Sended message
|
||||
|
||||
// Sending the account parameters menu
|
||||
telegram::account_parameters($context);
|
||||
});
|
||||
} else {
|
||||
// Not writed the account instance into the ArangoDB document
|
||||
|
||||
// Sending the message
|
||||
$context->sendMessage('⚠️ *Не удалось записать SIM\-номер*');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unset($app);
|
||||
});
|
||||
|
||||
$robot->onCommand('start', fn(context $context) => telegram::start($context));
|
||||
$robot->onCommand('contacts', fn(context $context) => telegram::contacts($context));
|
||||
$robot->onCommand('company', fn(context $context) => telegram::company($context));
|
||||
$robot->onCommand('community', fn(context $context) => telegram::community($context));
|
||||
$robot->onCommand('settings', fn(context $context) => telegram::settings($context));
|
||||
|
||||
$robot->onText('💬 Контакты', fn(context $context) => telegram::contacts($context));
|
||||
$robot->onText('🏛️ О компании', fn(context $context) => telegram::company($context));
|
||||
$robot->onText('🎯 Сообщество', fn(context $context) => telegram::community($context));
|
||||
$robot->onText('⚙️ Настройки', fn(context $context) => telegram::settings($context));
|
||||
|
||||
$robot->onCbQueryData(['contacts'], fn(context $context) => telegram::contacts($context));
|
||||
$robot->onCbQueryData(['company'], fn(context $context) => telegram::company($context));
|
||||
$robot->onCbQueryData(['community'], fn(context $context) => telegram::community($context));
|
||||
$robot->onCbQueryData(['settings'], fn(context $context) => telegram::settings($context));
|
||||
|
||||
$robot->onCbQueryData(['mail'], fn(context $context) => telegram::_mail($context));
|
||||
$robot->onCbQueryData(['import_request'], fn(context $context) => telegram::import_request($context));
|
||||
|
||||
$robot->onCbQueryData(['order_commentary_request'], fn(context $context) => telegram::order_commentary_request($context));
|
||||
$robot->onCbQueryData(['order'], fn(context $context) => telegram::order($context));
|
||||
|
||||
$robot->onCbQueryData(['tuning'], fn(context $context) => telegram::tuning($context));
|
||||
$robot->onCbQueryData(['brands'], fn(context $context) => telegram::brands($context));
|
||||
|
||||
$robot->onCbQueryData(['cart_delivery'], fn(context $context) => telegram::cart_delivery($context));
|
||||
|
||||
$robot->onCbQueryData(['delivery_registration_destination_office'], fn(context $context) => telegram::delivery_registration_destination_office($context));
|
||||
$robot->onCbQueryData(['delivery_registration_destination_door'], fn(context $context) => telegram::delivery_registration_destination_door($context));
|
||||
$robot->onCbQueryData(['delivery_registration_sim_input'], fn(context $context) => telegram::delivery_registration_sim_input($context));
|
||||
$robot->onCbQueryData(['delivery_registration_name_input'], fn(context $context) => telegram::delivery_registration_name_input($context));
|
||||
$robot->onCbQueryData(['delivery_registration_address_input'], fn(context $context) => telegram::delivery_registration_address_input($context));
|
||||
$robot->onCbQueryData(['delivery_registration'], fn(context $context) => telegram::delivery_registration($context));
|
||||
|
||||
$robot->onCbQueryData(['account_parameters_force'], fn(context $context) => telegram::account_parameters($context, force: true));
|
||||
|
||||
$robot->onCbQueryData(['receiver_sim_choose'], fn(context $context) => telegram::receiver_sim_choose($context));
|
||||
$robot->onCbQueryData(['receiver_sim_request'], fn(context $context) => telegram::receiver_sim_request($context));
|
||||
/* $robot->onCbQueryData(['receiver_sim_input'], fn(context $context) => telegram::receiver_sim_input($context)); */
|
||||
$robot->onCbQueryData(['receiver_sim_write'], fn(context $context) => telegram::receiver_sim_write($context));
|
||||
|
||||
$robot->onCbQueryData(['receiver_name_choose'], fn(context $context) => telegram::receiver_name_choose($context));
|
||||
$robot->onCbQueryData(['receiver_name_request'], fn(context $context) => telegram::receiver_name_request($context));
|
||||
/* $robot->onCbQueryData(['receiver_name_input'], fn(context $context) => telegram::receiver_name_input($context)); */
|
||||
$robot->onCbQueryData(['receiver_name_write'], fn(context $context) => telegram::receiver_name_write($context));
|
||||
|
||||
$robot->onCbQueryData(['receiver_destination_choose'], fn(context $context) => telegram::receiver_destination_choose($context));
|
||||
$robot->onCbQueryData(['receiver_destination_office'], fn(context $context) => telegram::receiver_destination_office($context));
|
||||
$robot->onCbQueryData(['receiver_destination_door'], fn(context $context) => telegram::receiver_destination_door($context));
|
||||
|
||||
$robot->onCbQueryData(['receiver_address_choose'], fn(context $context) => telegram::receiver_address_choose($context));
|
||||
$robot->onCbQueryData(['receiver_address_request_office'], fn(context $context) => telegram::receiver_address_request($context, office: true));
|
||||
$robot->onCbQueryData(['receiver_address_request_door'], fn(context $context) => telegram::receiver_address_request($context, office: false));
|
||||
/* $robot->onCbQueryData(['receiver_address_input'], fn(context $context) => telegram::receiver_address_input($context)); */
|
||||
$robot->onCbQueryData(['receiver_address_write'], fn(context $context) => telegram::receiver_address_write($context));
|
||||
|
||||
$robot->onException(function (Context $context, $exception) {
|
||||
var_dump($exception);
|
||||
});
|
||||
|
||||
// Инициализация middleware с обработкой аккаунта
|
||||
$robot->middleware([telegram::class, "account"]);
|
||||
|
||||
// Инициализация middleware с обработкой технических работ разных уровней
|
||||
$robot->middleware([telegram::class, "suspension"]);
|
||||
|
||||
// Запуск чат-робота
|
||||
$robot->run();
|
|
@ -1,384 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\huesos;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\huesos\controllers\core as controller,
|
||||
mirzaev\huesos\models\core as model,
|
||||
mirzaev\huesos\models\socket;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\core;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\connection as arangodb,
|
||||
mirzaev\arangodb\collection,
|
||||
mirzaev\arangodb\document;
|
||||
|
||||
// Library for ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
||||
// Server of WebSocket
|
||||
use OpenSwoole\WebSocket\{Server, Frame};
|
||||
use OpenSwoole\Constant,
|
||||
OpenSwoole\Http\Request,
|
||||
OpenSwoole\Table;
|
||||
|
||||
// Initialize dependencies
|
||||
require __DIR__ . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. 'vendor' . DIRECTORY_SEPARATOR
|
||||
. 'autoload.php';
|
||||
|
||||
define('INDEX', __DIR__);
|
||||
|
||||
// Initialize the core
|
||||
$core = new core(namespace: __NAMESPACE__, controller: new controller(false), model: $model = new model(false));
|
||||
|
||||
// https://dev.to/robertobutti/websocket-with-php-4k2c
|
||||
|
||||
$server = new Server("armng.dev.mirzaev.sexy", 9502, Server::SIMPLE_MODE, Constant::SOCK_TCP | Constant::SSL);
|
||||
|
||||
$accounts = new Table(1024);
|
||||
$accounts->column('id', Table::TYPE_INT, 4);
|
||||
$accounts->column('type', Table::TYPE_STRING, 16);
|
||||
$accounts->create();
|
||||
|
||||
$server->set([
|
||||
// Process
|
||||
'daemonize' => 1,
|
||||
'user' => 'www-data',
|
||||
'group' => 'www-data',
|
||||
/* 'chroot' => '/data/server/', */
|
||||
'open_cpu_affinity' => true,
|
||||
/* 'cpu_affinity_ignore' => [0, 1], */
|
||||
'pid_file' => '/var/run/php/ebala-socket.pid',
|
||||
|
||||
// Server
|
||||
/* 'reactor_num' => 8,
|
||||
'worker_num' => 2, */
|
||||
'message_queue_key' => 'mq1',
|
||||
'dispatch_mode' => 4,
|
||||
'discard_timeout_request' => true,
|
||||
/* 'dispatch_func' => 'my_dispatch_function', */
|
||||
|
||||
// Worker
|
||||
/* 'max_request' => 0,
|
||||
'max_request_grace' => $max_request / 2, */
|
||||
|
||||
// HTTP Server max execution time, since v4.8.0
|
||||
'max_request_execution_time' => 5, // 30s
|
||||
|
||||
// Task worker
|
||||
/* 'task_ipc_mode' => 2,
|
||||
'task_max_request' => 100,
|
||||
'task_tmpdir' => '/tmp',
|
||||
'task_worker_num' => 8,
|
||||
'task_enable_coroutine' => true,
|
||||
'task_use_object' => true, */
|
||||
|
||||
// Logging
|
||||
'log_level' => 1,
|
||||
'log_file' => __DIR__ . '/../logs/openswoole.log',
|
||||
'log_rotation' => Constant::LOG_ROTATION_DAILY,
|
||||
'log_date_format' => '%Y-%m-%d %H:%M:%S',
|
||||
'log_date_with_microseconds' => false,
|
||||
/* 'request_slowlog_file' => false, */
|
||||
|
||||
// Enable trace logs
|
||||
'trace_flags' => Constant::TRACE_ALL,
|
||||
|
||||
// TCP
|
||||
'input_buffer_size' => 2097152,
|
||||
'buffer_output_size' => 32 * 1024 * 1024, // byte in unit
|
||||
'tcp_fastopen' => true,
|
||||
'max_conn' => 10000,
|
||||
'tcp_defer_accept' => false,
|
||||
'open_tcp_keepalive' => true,
|
||||
'tcp_keepidle' => 30, // Check if there is no data for 4s.
|
||||
/* 'tcp_keepinterval' => 1, // Check if there is data every 1s */
|
||||
'tcp_keepcount' => 10,
|
||||
'open_tcp_nodelay' => true,
|
||||
/* 'pipe_buffer_size' => 32 * 1024 * 1024, */
|
||||
'socket_buffer_size' => 128 * 1024 * 1024,
|
||||
|
||||
// Kernel
|
||||
'backlog' => 512,
|
||||
'kernel_socket_send_buffer_size' => 65535,
|
||||
'kernel_socket_recv_buffer_size' => 65535,
|
||||
|
||||
// TCP Parser
|
||||
'open_eof_check' => true,
|
||||
'open_eof_split' => true,
|
||||
'package_eof' => '\r\n',
|
||||
'open_length_check' => true,
|
||||
'package_length_type' => 'N',
|
||||
'package_body_offset' => 8,
|
||||
'package_length_offset' => 8,
|
||||
'package_max_length' => 2 * 1024 * 1024, // 2MB
|
||||
/* 'package_length_func' => 'my_package_length_func', */
|
||||
|
||||
// Coroutine
|
||||
'enable_coroutine' => true,
|
||||
'max_coroutine' => 3000,
|
||||
'send_yield' => true,
|
||||
|
||||
// tcp server
|
||||
'heartbeat_idle_time' => 600,
|
||||
'heartbeat_check_interval' => 30,
|
||||
'enable_delay_receive' => false,
|
||||
'enable_reuse_port' => false,
|
||||
'enable_unsafe_event' => false,
|
||||
|
||||
// Protocol
|
||||
'open_http_protocol' => true,
|
||||
'open_http2_protocol' => true,
|
||||
'open_websocket_protocol' => true,
|
||||
'open_mqtt_protocol' => false,
|
||||
|
||||
// HTTP2
|
||||
'http2_header_table_size' => 4095,
|
||||
'http2_initial_window_size' => 65534,
|
||||
'http2_max_concurrent_streams' => 1281,
|
||||
'http2_max_frame_size' => 16383,
|
||||
'http2_max_header_list_size' => 4095,
|
||||
|
||||
// SSL
|
||||
'ssl_cert_file' => '/etc/letsencrypt/live/xn--80aksgi6f.xn--24-mlca2chbdebu6a.xn--p1ai/fullchain.pem',
|
||||
'ssl_key_file' => '/etc/letsencrypt/live/xn--80aksgi6f.xn--24-mlca2chbdebu6a.xn--p1ai/privkey.pem',
|
||||
'ssl_ciphers' => 'ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP',
|
||||
'ssl_protocols' => Constant::SSL_TLSv1_3, // added from v4.5.4
|
||||
'ssl_verify_peer' => false,
|
||||
/* 'ssl_sni_certs' => [
|
||||
"cs.php.net" => [
|
||||
'ssl_cert_file' => __DIR__ . "/config/sni_server_cs_cert.pem",
|
||||
'ssl_key_file' => __DIR__ . "/config/sni_server_cs_key.pem"
|
||||
],
|
||||
"uk.php.net" => [
|
||||
'ssl_cert_file' => __DIR__ . "/config/sni_server_uk_cert.pem",
|
||||
'ssl_key_file' => __DIR__ . "/config/sni_server_uk_key.pem"
|
||||
],
|
||||
"us.php.net" => [
|
||||
'ssl_cert_file' => __DIR__ . "/config/sni_server_us_cert.pem",
|
||||
'ssl_key_file' => __DIR__ . "/config/sni_server_us_key.pem",
|
||||
],
|
||||
], */
|
||||
|
||||
// Static Files
|
||||
'document_root' => __DIR__,
|
||||
'enable_static_handler' => true,
|
||||
/* 'static_handler_locations' => ['/static', '/app/images'], */
|
||||
'http_index_files' => ['index.html', 'index.txt'],
|
||||
|
||||
// Source File Reloading
|
||||
'reload_async' => true,
|
||||
'max_wait_time' => 30,
|
||||
|
||||
// HTTP Server
|
||||
'http_parse_post' => true,
|
||||
'http_parse_cookie' => true,
|
||||
'upload_tmp_dir' => '/tmp',
|
||||
|
||||
// Compression
|
||||
'http_compression' => true,
|
||||
'http_compression_level' => 3, // 1 - 9
|
||||
'compression_min_length' => 20,
|
||||
|
||||
// Websocket
|
||||
'websocket_compression' => true,
|
||||
'open_websocket_close_frame' => true,
|
||||
'open_websocket_ping_frame' => true, // added from v4.5.4
|
||||
'open_websocket_pong_frame' => true, // added from v4.5.4
|
||||
|
||||
// TCP User Timeout
|
||||
'tcp_user_timeout' => 120,
|
||||
|
||||
// DNS Server
|
||||
'dns_server' => '1.1.1.1',
|
||||
/* 'dns_cache_refresh_time' => 60, */
|
||||
/* 'enable_preemptive_scheduler' => 0, */
|
||||
|
||||
/* 'open_fastcgi_protocol' => 0, */
|
||||
'open_redis_protocol' => 0,
|
||||
|
||||
'event_object' => false,
|
||||
]);
|
||||
|
||||
/* $server->set([
|
||||
'ssl_cert_file' => __DIR__
|
||||
. DIRECTORY_SEPARATOR . '..'
|
||||
. DIRECTORY_SEPARATOR . 'settings'
|
||||
. DIRECTORY_SEPARATOR . 'certificates'
|
||||
. DIRECTORY_SEPARATOR . '84.22.137.106.pem',
|
||||
'ssl_key_file' => __DIR__
|
||||
. DIRECTORY_SEPARATOR . '..'
|
||||
. DIRECTORY_SEPARATOR . 'settings'
|
||||
. DIRECTORY_SEPARATOR . 'certificates'
|
||||
. DIRECTORY_SEPARATOR . '84.22.137.106-key.pem'
|
||||
]); */
|
||||
|
||||
$server->on("Start", function (Server $server) use ($core, $model) {
|
||||
// Очистка баз данных для сокетов
|
||||
collection::truncate($model->arangodb->session, 'socket');
|
||||
collection::truncate($model->arangodb->session, 'session_edge_socket');
|
||||
|
||||
// Запись в буфер вывода (журнал)
|
||||
echo "Swoole WebSocket Server is started at " . $server->host . ":" . $server->port . "\n";
|
||||
});
|
||||
|
||||
$server->on('Open', function (Server $server, Request $request) use ($accounts, $core, $model) {
|
||||
// Инициализация идентификатора
|
||||
$id = $request->fd;
|
||||
|
||||
// Запись в базу данных
|
||||
$accounts->set((string) $id, [
|
||||
'id' => $id
|
||||
]);
|
||||
|
||||
// Запись в буфер вывода (журнал)
|
||||
echo '[' . date('Y.m.d h:i:s', time()) . "][$id] Connected ({$accounts->count()})" . PHP_EOL;
|
||||
|
||||
// Регистрация
|
||||
if (document::write($model->arangodb->session, 'socket', [
|
||||
'id' => (int) $id,
|
||||
'key' => $key = sodium_bin2hex(sodium_crypto_generichash((string) $id)),
|
||||
'expires' => (int) strtotime('+1 hour')
|
||||
])) {
|
||||
// Создана инстанция сокета (регистрация)
|
||||
|
||||
// Отправка данных для подключения
|
||||
$server->push($id, sprintf(
|
||||
<<<JSON
|
||||
{
|
||||
"type": "registration",
|
||||
"key": "%s"
|
||||
}
|
||||
JSON,
|
||||
$key
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
$server->on('Message', function (Server $server, Frame $frame) use ($accounts, $core, $model) {
|
||||
// Запись в буфер вывода (журнал)
|
||||
echo '[' . date('Y.m.d h:i:s', time()) . "][$frame->fd] Message: $frame->data" . PHP_EOL;
|
||||
|
||||
if ($account = socket::account($frame->fd)) {
|
||||
// Авторизован аккаунт
|
||||
|
||||
// Инициализация полученных данных
|
||||
$message = json_decode($frame->data, false, 2);
|
||||
|
||||
// Инициализация имени
|
||||
$name = trim((!empty($account->name['first']) ? mb_strtoupper(mb_substr($account->name['first'], 0, 1)) . '.' : '') . (!empty($account->name['last']) ? ' ' . mb_strtoupper(mb_substr($account->name['last'], 0, 1)) . '.' : '') . (!empty($account->name['second']) ? ' ' . $account->name['second'] : ''), ' ');
|
||||
|
||||
try {
|
||||
if ($message->target === 'task') {
|
||||
// Заявка
|
||||
|
||||
if ($message->type === 'block') {
|
||||
// Блокировка редактирования
|
||||
|
||||
if ($account->status() && ($account->type === 'administrator' || $account->type === 'operator'))
|
||||
if (task::block((int) $message->_key, (int) $account->getKey())) {
|
||||
// Заблокирована заявка
|
||||
|
||||
// Отправка сообщения всем сокетам
|
||||
foreach ($accounts as $key => $value)
|
||||
if ((int) $key !== $frame->fd)
|
||||
$server->push((int) $key, <<<JSON
|
||||
{
|
||||
"type": "blocked",
|
||||
"target": "task",
|
||||
"_key": $message->_key,
|
||||
"account": {
|
||||
"_key": {$account->getKey()},
|
||||
"name": "$name"
|
||||
}
|
||||
}
|
||||
JSON);
|
||||
}
|
||||
} else if ($message->type === 'unblock') {
|
||||
// Разблокировка редактирования
|
||||
|
||||
if ($account->status() && ($account->type === 'administrator' || $account->type === 'operator'))
|
||||
if (task::unblock((int) $message->_key, (int) $account->getKey())) {
|
||||
// Заблокирована заявка
|
||||
|
||||
|
||||
// Отправка сообщения всем сокетам
|
||||
foreach ($accounts as $key => $value)
|
||||
if ((int) $key !== $frame->fd)
|
||||
$server->push((int) $key, <<<JSON
|
||||
{
|
||||
"type": "unblocked",
|
||||
"target": "task",
|
||||
"_key": $message->_key,
|
||||
"account": {
|
||||
"_key": {$account->getKey()},
|
||||
"name": "$name"
|
||||
}
|
||||
}
|
||||
JSON);
|
||||
}
|
||||
} else if ($message->type === 'update') {
|
||||
// Обновление
|
||||
|
||||
// Отправка сообщения всем сокетам
|
||||
foreach ($accounts as $key => $value)
|
||||
if ((int) $key !== $frame->fd)
|
||||
$server->push((int) $key, <<<JSON
|
||||
{
|
||||
"type": "updated",
|
||||
"target": "task",
|
||||
"_key": $message->_key
|
||||
}
|
||||
JSON);
|
||||
} else if ($message->type === 'delete') {
|
||||
// Обновление
|
||||
|
||||
// Отправка сообщения всем сокетам
|
||||
foreach ($accounts as $key => $value)
|
||||
if ((int) $key !== $frame->fd)
|
||||
$server->push((int) $key, <<<JSON
|
||||
{
|
||||
"type": "deleted",
|
||||
"target": "task",
|
||||
"_key": $message->_key
|
||||
}
|
||||
JSON);
|
||||
}
|
||||
}
|
||||
} catch (exception $e) {
|
||||
var_dump($e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$server->on('Close', function (Server $server, int $id) use ($accounts, $core, $model) {
|
||||
// Удаление из базы данных
|
||||
$accounts->del((string) $id);
|
||||
|
||||
// Запись в буфер вывода (журнал)
|
||||
echo '[' . date('Y.m.d h:i:s', time()) . "][$id] Disconnect (server) ({$accounts->count()})" . PHP_EOL;
|
||||
});
|
||||
|
||||
$server->on('Disconnect', function (Server $server, int $id) use ($accounts, $core, $model) {
|
||||
// Удаление из базы данных
|
||||
$accounts->del((string) $id);
|
||||
|
||||
// Запись в буфер вывода (журнал)
|
||||
echo '[' . date('Y.m.d h:i:s', time()) . "][$id] Disconnect (client) ({$accounts->count()})" . PHP_EOL;
|
||||
});
|
||||
|
||||
// Запуск (точка входа)
|
||||
$server->start();
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
section#account {
|
||||
z-index: 999;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
nav#menu>a#account>img {
|
||||
position: absolute;
|
||||
width: auto;
|
||||
height: inherit;
|
||||
object-fit: contain;
|
||||
border-radius: 100%;
|
||||
/* border: 2px solid var(--tg-theme-bottom-bar-bg-color); */
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
main {
|
||||
--offset-bottom: 2rem;
|
||||
justify-content: center;
|
||||
gap: unset;
|
||||
}
|
||||
|
||||
main>:is(:first-child, :last-child) {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
main>h2 {
|
||||
margin: unset;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.2rem;
|
||||
color: var(--tg-theme-accent-text-color);
|
||||
}
|
||||
|
||||
main>h2+small {
|
||||
margin-bottom: calc(0 - var(--offset-bottom));
|
||||
color: var(--tg-theme-subtitle-text-color);
|
||||
}
|
||||
|
||||
main>#closing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: var(--offset-bottom);
|
||||
}
|
||||
|
||||
main>#closing>i.icon:first-child {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
main>#closing:after {
|
||||
content: ': ' var(--iterator);
|
||||
font-weight: bold;
|
||||
color: var(--tg-theme-accent-text-color);
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
main {
|
||||
--offset-bottom: 2rem;
|
||||
justify-content: center;
|
||||
gap: unset;
|
||||
}
|
||||
|
||||
main>:is(:first-child, :last-child) {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
main>h2 {
|
||||
margin: unset;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.2rem;
|
||||
color: var(--tg-theme-accent-text-color);
|
||||
}
|
||||
|
||||
main>h2+small {
|
||||
margin-bottom: calc(0 - var(--offset-bottom));
|
||||
color: var(--tg-theme-subtitle-text-color);
|
||||
}
|
||||
|
||||
main>#closing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: var(--offset-bottom);
|
||||
}
|
||||
|
||||
main>#closing>i.icon:first-child {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
main>#closing:after {
|
||||
content: ': ' var(--iterator);
|
||||
font-weight: bold;
|
||||
color: var(--tg-theme-accent-text-color);
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
@keyframes slide-down {
|
||||
0% {
|
||||
transform: translate(0, -100%);
|
||||
clip-path: polygon(0% 100%, 100% 100%, 100% 200%, 0% 200%);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(0, 0%);
|
||||
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.slide.down.animated {
|
||||
animation-duration: var(--animation-duration, 0.25s);
|
||||
animation-name: slide-down;
|
||||
animation-fill-mode: forwards;
|
||||
animation-timing-function: var(--animatiom-timing, ease-out);
|
||||
}
|
||||
|
||||
@keyframes slide-down-revert {
|
||||
0% {
|
||||
transform: translate(0, 0%);
|
||||
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(0, -100%);
|
||||
clip-path: polygon(0% 100%, 100% 100%, 100% 200%, 0% 200%);
|
||||
}
|
||||
}
|
||||
|
||||
.slide.down.revert.animated {
|
||||
animation-duration: var(--animation-duration, 0.25s);
|
||||
animation-name: slide-down-revert;
|
||||
animation-fill-mode: forwards;
|
||||
animation-timing-function: var(--animatiom-timing, ease-in);
|
||||
}
|
||||
|
||||
@keyframes opacity {
|
||||
0% {
|
||||
opacity: 0%;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.opacity.animated {
|
||||
animation-duration: var(--animation-duration, 0.25s);
|
||||
animation-name: opacity;
|
||||
animation-fill-mode: forwards;
|
||||
animation-timing-function: var(--animatiom-timing, ease-in);
|
||||
animation-delay: var(--animation-delay, 0s);
|
||||
}
|
|
@ -1,354 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
main>button#order,
|
||||
main>section:is(#summary, #products, #delivery, #delivery_request) {
|
||||
width: var(--width);
|
||||
gap: var(--gap);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
main:not(:has(h2#title + section#delivery))>h2#title {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
main>section:is(#summary, #delivery) {
|
||||
background-color: var(--tg-theme-section-bg-color);
|
||||
}
|
||||
|
||||
main>section#delivery>div.column {
|
||||
padding: 1rem;
|
||||
gap: var(--gap);
|
||||
}
|
||||
|
||||
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; */
|
||||
filter: brightness(0.6);
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#deliveries>input+label:is(:hover, :focus) {
|
||||
filter: brightness(1.3);
|
||||
/* 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: unset;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#deliveries>input:checked+label:is(:hover, :focus) {
|
||||
filter: brightness(1.5);
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#deliveries>input:checked+label:active {
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>section {
|
||||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>section.cdek#address>div {
|
||||
flex-flow: row wrap;
|
||||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>section.cdek#address>div.merged {
|
||||
margin-top: -0.4rem;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>section.cdek#address>div>input#location {
|
||||
min-width: max(6rem, 20%);
|
||||
width: min(6rem, 100%);
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>section.cdek#address>div>input#street {
|
||||
min-width: max(10rem, 30%);
|
||||
width: min(10rem, 100%);
|
||||
flex-grow: 10;
|
||||
}
|
||||
|
||||
|
||||
main>section#delivery>div.column>section.cdek#address>div>input#apartament {
|
||||
width: min(4rem, 100%);
|
||||
flex-shrink: 1;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>section.cdek>div#map {
|
||||
height: 203px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>section.cdek>div#map * {
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>section.cdek>div#map:not(.office, .door)>div>div:first-child,
|
||||
main>section#delivery>div.column>section.cdek>div#map>div>div:last-child a[href="https://cdek.ru"],
|
||||
main>section#delivery>div.column>section.cdek>div#map>div>div:last-child>div:first-child>div>ymaps>ymaps:nth-of-type(3)>ymaps:last-child,
|
||||
main>section#delivery>div.column>section.cdek>div#map>div>div:last-child>div:first-child>div>ymaps>ymaps:nth-of-type(4),
|
||||
main>section#delivery>div.column>section.cdek>div#map>div>div:last-child>div:first-child>div>ymaps>ymaps:last-of-type {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>section.cdek>div#map:not(.office, .door)>div>div:last-child>div:first-child>div>ymaps>ymaps:is(:nth-of-type(2), :nth-of-type(4)),
|
||||
main>section#delivery>div.column>section.cdek>div#map:is(.office, .door)>div>div:last-child>div:first-child>div>ymaps>ymaps:is(:nth-of-type(1), :nth-of-type(3)) {
|
||||
margin-bottom: unset !important;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>section.cdek>div#map.office.door>div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>section.cdek>div#map.office.door>div>div:first-child {
|
||||
order: 2;
|
||||
margin-bottom: unset !important;
|
||||
display: flex !important;
|
||||
padding: unset !important;
|
||||
flex-flow: row wrap;
|
||||
gap: 0.7rem !important;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>section.cdek>div#map.office.door>div>div:first-child> :is(div, div:is(:hover, :focus, :active)) {
|
||||
margin: unset !important;
|
||||
min-width: calc(50% - 0.7rem);
|
||||
padding: 0.2rem 1rem;
|
||||
flex-grow: 1;
|
||||
border-radius: 0.75rem !important;
|
||||
background-color: var(--tg-theme-button-color) !important;
|
||||
filter: unset;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>section.cdek>div#map.office.door>div>div:first-child>div:is(:hover, :focus) {
|
||||
filter: brightness(1.5);
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>section.cdek>div#map.office.door>div>div:first-child>div:active {
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>section.cdek>div#map.office.door>div>div:first-child label {
|
||||
color: var(--tg-theme-text-color);
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>section.cdek>div#map>div>div:last-child {
|
||||
border: unset !important;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>section.cdek>div#map:not(.office, .door)>div>div:last-child {
|
||||
height: 100% !important
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>section.cdek>div#map.office.door>div>div:last-child {
|
||||
order: 1;
|
||||
height: calc(100% - 3.375rem - 0.5rem) !important;
|
||||
border-radius: 0.75rem !important;
|
||||
}
|
||||
|
||||
/* main>section#delivery>div.column:not(:has(div#deliveries>input:checked))#address>div {
|
||||
display: none;
|
||||
} */
|
||||
|
||||
main:has(section#summary.disabled:hover)>section#delivery>div {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
section#delivery.hidden+#delivery_request {
|
||||
display: none;
|
||||
}
|
||||
|
||||
main>section#delivery_request>p:only-of-type {
|
||||
margin: unset;
|
||||
width: 100%;
|
||||
height: 1rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
main>section#delivery_request>p:only-of-type>i.icon {
|
||||
/* color: var(--tg-theme-accent-text-color); */
|
||||
color: var(--tg-theme-text-color);
|
||||
}
|
||||
|
||||
main>section#delivery_request>p:only-of-type>span:only-of-type {
|
||||
margin: 0 auto;
|
||||
/* color: var(--tg-theme-accent-text-color); */
|
||||
color: var(--tg-theme-text-color);
|
||||
color: var(--tg-theme-hint-color);
|
||||
}
|
||||
|
||||
main>section#summary {
|
||||
margin-top: -0.8rem;
|
||||
margin-bottom: -0.5rem;
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
main>section#summary>div {
|
||||
container-type: inline-size;
|
||||
container-name: summary;
|
||||
height: 2rem;
|
||||
padding: 0 1rem;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
overflow: hidden;
|
||||
pointer-events: auto;
|
||||
border-radius: 1.375rem;
|
||||
}
|
||||
|
||||
main>section#summary>div>span {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
main>section#summary>div>span:first-of-type {
|
||||
/* margin-left: auto; */
|
||||
}
|
||||
|
||||
main>button#order {
|
||||
/* margin-left: auto; */
|
||||
}
|
||||
|
||||
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-section-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: 7rem;
|
||||
/* min-width: 5rem; */
|
||||
min-height: 100%;
|
||||
object-fit: cover;
|
||||
image-rendering: auto;
|
||||
border-radius: 0.75rem;
|
||||
border: 0.2rem solid var(--tg-theme-hint-color);
|
||||
box-shadow: -5px 0px 50px 10px rgba(0, 0, 0, 0.4);
|
||||
-webkit-box-shadow: -5px 0px 50px 10px rgba(0, 0, 0, 0.4);
|
||||
-moz-box-shadow: -5px 0px 50px 10px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
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.head>button {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
main>section#products>article.product>div>div>button:first-of-type {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
main>section#products>article.product>div>div>button {
|
||||
padding: 0.4rem;
|
||||
color: var(--tg-theme-text-color);
|
||||
background: unset;
|
||||
}
|
||||
|
||||
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;
|
||||
color: var(--tg-theme-text-color);
|
||||
}
|
||||
|
||||
@container summary (max-width: 450px) {
|
||||
main>section#summary>div>span.days {
|
||||
--days: var(--days-short);
|
||||
}
|
||||
|
||||
/* main>section#summary>div>span:not(#cost, #cost_delivery, #shipping) {
|
||||
display: none;
|
||||
} */
|
||||
}
|
|
@ -1,346 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
main>section#categories:has(a),
|
||||
main>section#categories:not(:has(a))>ul:has(li.category[type="button"]) {
|
||||
width: var(--width);
|
||||
display: flex;
|
||||
flex-flow: var(--catalog-categories-wrap-flex-wrap, row wrap);
|
||||
flex-direction: var(--catalog-categories-wrap-flex-direction, 'row');
|
||||
}
|
||||
|
||||
main>section#categories:has(a) {
|
||||
gap: var(--gap, 5px);
|
||||
}
|
||||
|
||||
main>section#categories:not(:has(a))>ul:has(li.category[type="button"])>ul {
|
||||
margin-bottom: var(--gap, 5px);
|
||||
}
|
||||
|
||||
main>section#categories ul {
|
||||
padding-left: 1.3rem;
|
||||
list-style-type: none;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
main>section#categories>li {
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
main>section#categories li {
|
||||
/* font-family: "Chalet"; */
|
||||
}
|
||||
|
||||
main>section#categories li.openable {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
main>section#categories ul>li:has(+ ul) {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
main>section#categories ul>ul {
|
||||
--offset: 1rem;
|
||||
--padding: 1rem;
|
||||
margin-top: calc(var(--offset, 1rem) * -1);
|
||||
padding-top: calc(var(--offset, 1rem) + var(--padding, 1rem));
|
||||
padding-bottom: var(--padding, 1rem);
|
||||
border-radius: 0 0 0.75rem 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3rem;
|
||||
background: var(--catalog-categories-buttons-lists-background-color, var(--tg-theme-secondary-bg-color));
|
||||
}
|
||||
|
||||
main>section#categories:not(:has(a))>ul:has(li.category[type="button"]) {
|
||||
margin: unset;
|
||||
padding: unset;
|
||||
}
|
||||
|
||||
main>section#categories>a.category[type="button"],
|
||||
main>section#categories>ul>li.category[type="button"] {
|
||||
--padding: 0.7rem;
|
||||
z-index: unset;
|
||||
position: relative;
|
||||
height: 23px;
|
||||
padding: var(--padding);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
border-radius: 0.75rem;
|
||||
color: var(--tg-theme-button-text-color);
|
||||
background: var(--catalog-categories-buttons-background);
|
||||
}
|
||||
|
||||
main>section#categories>a.category[type="button"]:not(.column):after,
|
||||
main>section#categories>ul>li.category[type="button"]:not(.column):after {
|
||||
z-index: -100;
|
||||
position: absoulute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
filter: brightness(0.8);
|
||||
background-color: var(--tg-theme-button-color);
|
||||
}
|
||||
|
||||
main>section#categories:last-child {
|
||||
/* margin-bottom: unset; */
|
||||
}
|
||||
|
||||
/* main>section#categories>a.category[type="button"]:has(>img) {
|
||||
min-width: calc(50% - var(--padding) * 2);
|
||||
height: 180px;
|
||||
padding: unset;
|
||||
} */
|
||||
|
||||
main>section#categories>a.category[type="button"],
|
||||
main>section#categories>ul>li.category[type="button"] {
|
||||
width: var(--catalog-categories-buttons-width, calc(50% - var(--padding) * 2));
|
||||
height: var(--catalog-categories-buttons-height, 180px);
|
||||
padding: unset;
|
||||
}
|
||||
|
||||
main>section#categories ul>li[type="button"].opened+ul {
|
||||
max-height: var(--catalog-categories-buttons-lists-height, 500px);
|
||||
transition: max-height 0.15s ease-in 0.1s, margin 0s ease-out 0.1s, padding 0s ease-out 0.1s;
|
||||
}
|
||||
|
||||
main>section#categories ul>li[type="button"]:not(.opened)+ul {
|
||||
max-height: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
margin-top: 0;
|
||||
transition: max-height 0.25s ease-out, margin 0s ease-out 0.25s, padding 0s ease-out 0.25s;
|
||||
}
|
||||
|
||||
main>section#categories>a.category[type="button"]>img,
|
||||
main>section#categories>ul>li.category[type="button"]>img {
|
||||
position: absolute;
|
||||
left: -5%;
|
||||
top: -5%;
|
||||
width: 110%;
|
||||
height: 110%;
|
||||
object-fit: cover;
|
||||
/* filter: blur(1px); */
|
||||
filter: var(--catalog-categories-buttons-images-filter, contrast(1.2) brightness(60%));
|
||||
}
|
||||
|
||||
main>section#categories>a.category[type="button"]>img.right,
|
||||
main>section#categories>ul>li.category[type="button"]>img.right {
|
||||
/* position: absolute; */
|
||||
left: unset;
|
||||
top: unset;
|
||||
right: 0;
|
||||
width: calc(100% - var(--catalog-categories-buttons-texts-width, 10rem));
|
||||
}
|
||||
|
||||
main>section#categories>a.category[type="button"]:is(:hover, :focus)>img,
|
||||
main>section#categories>ul>li.category[type="button"]:is(:hover, :focus)>img {
|
||||
filter: unset;
|
||||
}
|
||||
|
||||
/* main>section#categories>a.category[type="button"]:has(>img)>p { */
|
||||
main>section#categories>a.category[type="button"]>p,
|
||||
main>section#categories>ul>li.category[type="button"]>p {
|
||||
position: absolute;
|
||||
left: var(--padding);
|
||||
top: var(--catalog-categories-buttons-texts-top, var(--padding));
|
||||
bottom: var(--catalog-categories-buttons-texts-bottom, var(--padding));
|
||||
right: var(--padding);
|
||||
margin: unset;
|
||||
width: calc(var(--catalog-categories-buttons-texts-width, 10rem) - 0.7rem * 2);
|
||||
border-radius: 0.75rem;
|
||||
color: var(--catalog-categories-buttons-texts-title-color, var(--tg-theme-text-color));
|
||||
/* background: var(--tg-theme-hint-color); */
|
||||
}
|
||||
|
||||
main>section#categories>a.category[type="button"]>div.separator.gradient:has(+img.right),
|
||||
main>section#categories>ul>li.category[type="button"]>div.separator.gradient:has(+img.right) {
|
||||
--background: var(--catalog-categories-buttons-background, var(--tg-theme-button-color));
|
||||
z-index: 100;
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: calc(100% - var(--catalog-categories-buttons-texts-width, 10rem));
|
||||
height: 100%;
|
||||
background: var(--background);
|
||||
background: linear-gradient(90deg, var(--background) 0%, transparent var(--catalog-categories-buttons-separator-width, 100%));
|
||||
}
|
||||
|
||||
main>section#categories>a.category[type="button"]>p,
|
||||
main>section#categories>ul>li.category[type="button"]>p {
|
||||
z-index: 100;
|
||||
padding: 0 calc(var(--padding) / 2);
|
||||
}
|
||||
|
||||
main>section#categories>a.category[type="button"]>p:not(.background),
|
||||
main>section#categories>ul>li.category[type="button"]>p:not(.background) {
|
||||
font-family: "Cygre";
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
main>section#categories>a.category[type="button"]>p.background,
|
||||
main>section#categories>ul>li.category[type="button"]>p.background {
|
||||
padding: 8px calc(var(--padding, 0.7rem) * 1.3);
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
main>section#categories>a.category[type="button"]>p.background:after,
|
||||
main>section#categories>ul>li.category[type="button"]>p.background:after {
|
||||
z-index: -100;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 0.75rem;
|
||||
background-color: var(--tg-theme-button-color);
|
||||
box-shadow: 0px 0px 8px 4px rgba(0, 0, 0, 0.1);
|
||||
-webkit-box-shadow: 0px 0px 8px 4px rgba(0, 0, 0, 0.1);
|
||||
-moz-box-shadow: 0px 0px 8px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
main>section#filters {
|
||||
--filters-height: 2rem;
|
||||
width: var(--width);
|
||||
max-height: var(--filters-height);
|
||||
display: flex;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
main>section#products {
|
||||
--column: calc((100% - var(--gap)) / 2);
|
||||
width: var(--width);
|
||||
display: grid;
|
||||
grid-gap: var(--gap);
|
||||
grid-template-columns: repeat(2, var(--column));
|
||||
grid-auto-flow: row dense;
|
||||
}
|
||||
|
||||
main>section#products>div.column {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap);
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-grow: 0;
|
||||
flex-direction: column;
|
||||
border-radius: 0.75rem;
|
||||
overflow: clip;
|
||||
cursor: pointer;
|
||||
backdrop-filter: brightness(0.7);
|
||||
background-color: var(--tg-theme-section-bg-color);
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product:is(:hover, :focus) {
|
||||
/* flex-grow: 0.1; */
|
||||
/* background-color: var(--tg-theme-section-bg-color); */
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product:is(:hover, :focus)>* {
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product:not(:is(:hover, :focus))>* {
|
||||
transition: 0.2s ease-out;
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product>a>img:first-of-type {
|
||||
--border: 0.2rem;
|
||||
width: calc(100% - var(--border) * 2);
|
||||
image-rendering: auto;
|
||||
border-radius: 0.75rem;
|
||||
border: 0.2rem solid var(--tg-theme-hint-color);
|
||||
box-shadow: 0px 5px 70px 20px rgba(0, 0, 0, 0.3);
|
||||
-webkit-box-shadow: 0px 5px 70px 20px rgba(0, 0, 0, 0.3);
|
||||
-moz-box-shadow: 0px 5px 70px 20px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product>a>img:first-of-type+* {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product>a>p.title {
|
||||
z-index: 50;
|
||||
margin: unset;
|
||||
padding: 4px 8px 8px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: bold;
|
||||
overflow-wrap: anywhere;
|
||||
hyphens: auto;
|
||||
color: var(--tg-theme-text-color);
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product>a>p.title>span {
|
||||
margin-top: 0.2rem;
|
||||
display: block;
|
||||
font-weight: 400;
|
||||
font-size: small;
|
||||
color: var(--tg-theme-hint-color);
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product>div[data-product="buttons"]:last-of-type {
|
||||
z-index: 100;
|
||||
height: 33px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
/* main>section#products>div.column>article.product[data-product-amount]:not(:is([data-product-amount="0"], [data-product-amount="1"]))>div[data-product="buttons"]:last-of-type { */
|
||||
main>section#products>div.column>article.product[data-product-amount]:not(:is([data-product-amount="0"]))>div[data-product="buttons"]:last-of-type {
|
||||
container-type: inline-size;
|
||||
container-name: product-buttons;
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"] {
|
||||
padding: 0;
|
||||
flex-grow: 1;
|
||||
background-color: var(--catalog-button-cart-background, var(--tg-theme-button-color));
|
||||
}
|
||||
|
||||
/* main>section#products>div.column>article.product:is([data-product-amount="0"], [data-product-amount="1"])>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-parameter="amount"], */
|
||||
main>section#products>div.column>article.product:is([data-product-amount="0"])>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-parameter="amount"],
|
||||
main>section#products>div.column>article.product[data-product-amount="0"]>div[data-product="buttons"]>button:is([data-product-button="write"], [data-product-button="delete"]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-parameter="amount"]:after {
|
||||
content: '*';
|
||||
margin: 0 0.2rem;
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product[data-product-amount]:not([data-product-amount="0"])>div[data-product="buttons"]>button[data-product-button="toggle"] {
|
||||
background-color: var(--catalog-button-cart-added-background, var(--tg-theme-button-color));
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product[data-product-amount]:not([data-product-amount="0"])>div[data-product="buttons"] {
|
||||
/* hehe */
|
||||
filter: var(--catalog-button-cart-added-background, hue-rotate(calc(120deg + var(--hue-rotate-offset, 0deg))));
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product>div[data-product="buttons"]>button {
|
||||
background-color: var(--catalog-button-cart-added-background, var(--tg-theme-button-color));
|
||||
}
|
||||
|
||||
@container product-buttons (max-width: 200px) {
|
||||
main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span:is([data-product-parameter="cost"], [data-product-parameter="currency"]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-parameter="amount"]:after {
|
||||
content: unset;
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
main>section#categories ul>ul {
|
||||
--padding: 1rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
main>section#categories>ul ul>li {
|
||||
--padding-row: calc(var(--gap, 5px) * 1);
|
||||
position: relative;
|
||||
padding: var(--padding-row);
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
main>section#categories>ul ul>li>:is(small, i.icon):last-child {
|
||||
margin-left: auto;
|
||||
margin-right: 0.1rem;
|
||||
rotate: 0deg;
|
||||
transition: rotate 0.1s ease-out;
|
||||
font-family: monospace;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
main>section#categories>ul ul>li.opened>:is(small, i.icon):last-child {
|
||||
rotate: 90deg;
|
||||
transition: rotate 0.1s ease-in;
|
||||
}
|
||||
|
||||
main>section#categories>ul ul>li:after,
|
||||
main>section#categories>ul ul>li[type="button"].opened+ul:has(+ li)>li:last-of-type:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
bottom: 0px;
|
||||
width: calc(100% - var(--padding-row) * 2);
|
||||
display: block;
|
||||
align-self: center;
|
||||
border-bottom: 1px solid var(--catalog-categories-buttons-lists-separatpr-color, var(--tg-theme-hint-color));
|
||||
transition: bottom 0s linear 0s;
|
||||
}
|
||||
|
||||
main>section#categories>ul ul>li:last-of-type:not(.opened):after {
|
||||
bottom: -1rem;
|
||||
transition: bottom 0.1s ease-out 0.25s;
|
||||
}
|
||||
|
||||
main>section#categories>ul ul>li:last-of-type.opened:after {
|
||||
bottom: -1px;
|
||||
transition: bottom 0.1s ease-in;
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
main>section#categories ul>li:not(.opened):has(+ ul) {
|
||||
margin-bottom: 0;
|
||||
transition: margin 0.1s ease-out 0.25s;
|
||||
}
|
||||
|
||||
main>section#categories>ul>li.opened:has(+ ul) {
|
||||
margin-bottom: var(--gap, 5px);
|
||||
transition: margin 0.1s ease-in;
|
||||
}
|
||||
|
||||
main>section#categories ul>ul {
|
||||
--offset: 0rem;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
main>section#categories>ul ul>ul {
|
||||
margin-left: var(--gap, 5px);
|
||||
width: calc(100% - var(--gap, 5px));
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
section#connection {
|
||||
z-index: 999;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
section#connection>i#indicator {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: block;
|
||||
cursor: help;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
section#connection>small {
|
||||
margin-right: 7px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
color: var(--socket-text);
|
||||
}
|
||||
|
||||
section#connection>i#indicator.disconnected:not(.connected) {
|
||||
background-color: var(--socket-disconnected);
|
||||
}
|
||||
|
||||
section#connection>i#indicator.connected:not(.disconnected) {
|
||||
background-color: var(--socket-connected);
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
@font-face {
|
||||
font-family: 'Chalet';
|
||||
src: url("/themes/default/fonts/chalet/chalet.otf") format('opentype');
|
||||
font-weight: 600;
|
||||
font-style: bold;
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
@font-face {
|
||||
font-family: 'Cygre';
|
||||
src: url("/themes/default/fonts/cygre/Cygre-Bold.ttf");
|
||||
font-weight: 600;
|
||||
font-style: bold;
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
@font-face {
|
||||
font-family: 'DejaVu';
|
||||
src: url("/themes/default/fonts/dejavu/DejaVuLGCSans-ExtraLight.ttf");
|
||||
font-weight: 200;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'DejaVu';
|
||||
src: url("/themes/default/fonts/dejavu/DejaVuLGCSans.ttf");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'DejaVu';
|
||||
src: url("/themes/default/fonts/dejavu/DejaVuLGCSans-Oblique.ttf");
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'DejaVu';
|
||||
src: url("/themes/default/fonts/dejavu/DejaVuLGCSans-Bold.ttf");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'DejaVu';
|
||||
src: url("/themes/default/fonts/dejavu/DejaVuLGCSans-BoldOblique.ttf");
|
||||
font-weight: 500;
|
||||
font-style: italic;
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
@font-face {
|
||||
font-family: 'Kabrio';
|
||||
src: url("/themes/default/fonts/kabrio/Kabrio-Light.ttf");
|
||||
font-weight: 200;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Kabrio';
|
||||
src: url("/themes/default/fonts/kabrio/Kabrio-Regular.ttf");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Kabrio';
|
||||
src: url("/themes/default/fonts/kabrio/Kabrio-Italic.ttf");
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Kabrio';
|
||||
src: url("/themes/default/fonts/kabrio/Kabrio-Heavy.ttf");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Kabrio';
|
||||
src: url("/themes/default/fonts/kabrio/Kabrio-HeavyItalic.ttf");
|
||||
font-weight: 500;
|
||||
font-style: italic;
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
i.icon.arrow:not(.circle, .square) {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
i.icon.arrow:not(.circle, .square)::after,
|
||||
i.icon.arrow:not(.circle, .square)::before {
|
||||
content: "";
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
}
|
||||
|
||||
i.icon.arrow:not(.circle, .square)::after {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-top: 2px solid;
|
||||
border-right: 2px solid;
|
||||
transform: rotate(45deg);
|
||||
bottom: 7px;
|
||||
}
|
||||
|
||||
i.icon.arrow:not(.circle, .square, .short)::before {
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
bottom: 10px;
|
||||
background: currentColor;
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
i.icon.arrow.circle {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: 2px solid;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
i.icon.arrow.circle::after,
|
||||
i.icon.arrow.circle::before {
|
||||
content: "";
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
i.icon.arrow.circle::after {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-top: 2px solid;
|
||||
border-right: 2px solid;
|
||||
transform: rotate(45deg);
|
||||
bottom: 6px;
|
||||
}
|
||||
|
||||
i.icon.arrow.circle::before {
|
||||
width: 10px;
|
||||
height: 2px;
|
||||
bottom: 8px;
|
||||
background: currentColor;
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
@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);
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
i.icon.corner {
|
||||
position: relative;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
i.icon.corner::after,
|
||||
i.icon.corner::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
i.icon.corner.left::after,
|
||||
i.icon.corner.left::before {
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
i.icon.corner.right::after,
|
||||
i.icon.corner.right::before {
|
||||
right: 3px;
|
||||
}
|
||||
|
||||
i.icon.corner::after {
|
||||
bottom: 3px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
i.icon.corner.left::after {
|
||||
border-left: 2px solid;
|
||||
border-bottom: 2px solid;
|
||||
}
|
||||
|
||||
i.icon.corner.right::after {
|
||||
border-top: 2px solid;
|
||||
border-right: 2px solid;
|
||||
}
|
||||
|
||||
i.icon.corner::before {
|
||||
bottom: 6px;
|
||||
width: 16px;
|
||||
height: 12px;
|
||||
border-bottom: 2px solid;
|
||||
}
|
||||
|
||||
i.icon.corner.left::before {
|
||||
border-bottom-right-radius: 4px;
|
||||
border-right: 2px solid;
|
||||
}
|
||||
|
||||
i.icon.corner.right::before {
|
||||
border-bottom-left-radius: 4px;
|
||||
border-left: 2px solid;
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
i.icon.hashtag {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
display: block;
|
||||
transform: scale(1);
|
||||
width: 8px;
|
||||
height: 16px;
|
||||
border-left: 2px solid;
|
||||
border-right: 2px solid;
|
||||
}
|
||||
|
||||
i.icon.hashtag::before {
|
||||
content: "";
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 8px;
|
||||
border-top: 2px solid;
|
||||
border-bottom: 2px solid;
|
||||
left: -6px;
|
||||
top: 4px;
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
i.icon.house {
|
||||
position: relative;
|
||||
margin-bottom: -7px;
|
||||
width: 18px;
|
||||
height: 14px;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
border: 2px solid;
|
||||
border-top: 0;
|
||||
border-bottom: 0;
|
||||
border-top-right-radius: 3px;
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
background:
|
||||
linear-gradient(to left, currentColor 5px, transparent 0) no-repeat 0 bottom/4px 2px,
|
||||
linear-gradient(to left, currentColor 5px, transparent 0) no-repeat right bottom/4px 2px;
|
||||
}
|
||||
|
||||
i.icon.house::after,
|
||||
i.icon.house::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
i.icon.house::before {
|
||||
left: 0;
|
||||
top: -5px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 3px;
|
||||
transform: rotate(45deg);
|
||||
border-top: 2px solid;
|
||||
border-left: 2px solid;
|
||||
border-top-left-radius: 4px;
|
||||
}
|
||||
|
||||
i.icon.house::after {
|
||||
left: 3px;
|
||||
bottom: 0;
|
||||
width: 8px;
|
||||
height: 10px;
|
||||
border: 2px solid;
|
||||
border-radius: 100px;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom: 0;
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
i.icon.list.add {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 12px;
|
||||
height: 6px;
|
||||
border-top: 0 solid transparent;
|
||||
border-bottom: 2px solid transparent;
|
||||
box-shadow:
|
||||
inset 0 -2px 0,
|
||||
-2px 4px 0 -2px,
|
||||
0 -2px 0 0;
|
||||
}
|
||||
|
||||
i.icon.list.add.small {
|
||||
width: 10px;
|
||||
box-shadow:
|
||||
inset 0 -2px 0,
|
||||
-1px 3px 0 -1px,
|
||||
0 -2px 0 0;
|
||||
}
|
||||
|
||||
i.icon.list.add::after,
|
||||
i.icon.list.add::before {
|
||||
content: "";
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 2px;
|
||||
background: currentColor;
|
||||
top: 6px;
|
||||
right: -8px;
|
||||
}
|
||||
|
||||
i.icon.list.add.small::after,
|
||||
i.icon.list.add.small::before {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
i.icon.list.add::before {
|
||||
width: 2px;
|
||||
height: 10px;
|
||||
top: 2px;
|
||||
right: -4px;
|
||||
}
|
||||
|
||||
i.icon.list.add.small::before {
|
||||
width: 2px;
|
||||
height: 8px;
|
||||
top: 3px;
|
||||
right: -5px;
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
@keyframes loading_spinner {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
|
||||
i.icon.loading.spinner,
|
||||
i.icon.loading.spinner::before {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
i.icon.loading.spinner::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-radius: 100px;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: currentColor;
|
||||
}
|
||||
|
||||
i.icon.loading.spinner.animated::before {
|
||||
animation: loading_spinner 1s cubic-bezier(0.6, 0, 0.4, 1) infinite;
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
i.icon.minus {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
background: currentColor;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
i.icon.minus.small {
|
||||
width: 10px;
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
i.icon.pin {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
margin-top: -4px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: block;
|
||||
border: 2px solid;
|
||||
border-radius: 100% 100% 0 100%;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
i.icon.pin.small {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
i.icon.pin::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 3px;
|
||||
top: 3px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
border: 2px solid;
|
||||
border-radius: 40px;
|
||||
}
|
||||
|
||||
i.icon.pin.small::before {
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
i.icon.plus,
|
||||
i.icon.plus::after {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
background: currentColor;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
i.icon.plus {
|
||||
margin-top: -2px;
|
||||
position: relative;
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
i.icon.plus.small {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
i.icon.plus::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 16px;
|
||||
top: -7px;
|
||||
left: 7px;
|
||||
}
|
||||
|
||||
i.icon.plus.small::after {
|
||||
height: 10px;
|
||||
top: -4px;
|
||||
left: 4px;
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
i.icon.search {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
display: block;
|
||||
transform: scale(1);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid;
|
||||
border-radius: 100%;
|
||||
margin-left: -4px;
|
||||
margin-top: -4px;
|
||||
}
|
||||
|
||||
i.icon.search::after {
|
||||
content: "";
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
border-radius: 3px;
|
||||
width: 2px;
|
||||
height: 8px;
|
||||
background: currentColor;
|
||||
transform: rotate(-45deg);
|
||||
top: 10px;
|
||||
left: 12px;
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
i.icon.shopping.cart {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 21px;
|
||||
background:
|
||||
linear-gradient(to left, currentColor 12px, transparent 0) no-repeat -1px 6px/18px 2px,
|
||||
linear-gradient(to left, currentColor 12px, transparent 0) no-repeat 6px 14px/11px 2px,
|
||||
linear-gradient(to left, currentColor 12px, transparent 0) no-repeat 0 2px/4px 2px,
|
||||
radial-gradient(circle, currentColor 60%, transparent 40%) no-repeat 12px 17px/4px 4px,
|
||||
radial-gradient(circle, currentColor 60%, transparent 40%) no-repeat 6px 17px/4px 4px;
|
||||
}
|
||||
|
||||
i.icon.shopping.cart::after,
|
||||
i.icon.shopping.cart::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
width: 2px;
|
||||
height: 14px;
|
||||
background: currentColor;
|
||||
top: 2px;
|
||||
left: 4px;
|
||||
transform: skew(12deg);
|
||||
}
|
||||
|
||||
i.icon.shopping.cart::after {
|
||||
height: 10px;
|
||||
top: 6px;
|
||||
left: 16px;
|
||||
transform: skew(-12deg);
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
i.icon.trash {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 10px;
|
||||
height: 12px;
|
||||
border: 2px solid transparent;
|
||||
box-shadow:
|
||||
0 0 0 2px,
|
||||
inset -2px 0 0,
|
||||
inset 2px 0 0;
|
||||
border-bottom-left-radius: 1px;
|
||||
border-bottom-right-radius: 1px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
i.icon.trash.small {
|
||||
height: 9px;
|
||||
}
|
||||
|
||||
i.icon.trash::after,
|
||||
i.icon.trash::before {
|
||||
content: "";
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
i.icon.trash::after {
|
||||
background: currentColor;
|
||||
border-radius: 3px;
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
top: -4px;
|
||||
left: -5px;
|
||||
}
|
||||
|
||||
i.icon.trash::before {
|
||||
width: 10px;
|
||||
height: 4px;
|
||||
border: 2px solid;
|
||||
border-bottom: transparent;
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
top: -7px;
|
||||
left: -2px;
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
i.icon.user {
|
||||
width: 12px;
|
||||
height: 18px;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
i.icon.user::after,
|
||||
i.icon.user::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
border: 2px solid;
|
||||
}
|
||||
|
||||
i.icon.user::before {
|
||||
left: 2px;
|
||||
top: 0;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
i.icon.user::after {
|
||||
top: 9px;
|
||||
width: 12px;
|
||||
height: 9px;
|
||||
border-bottom: 0;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
@keyframes initialization {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
*:not(#loading) {
|
||||
animation: 0.2s ease-in 2s 1 normal forwards running initialization;
|
||||
/* animation: 0.2s ease-in 1s forwards initialization; */
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
div[data-type="select"] {
|
||||
--filter-width: max(14rem, 30vw);
|
||||
--filter-button-width: 2.5rem;
|
||||
--filter-height-element: var(--filters-height, 2rem);
|
||||
--filter-height-close: var(--filter-height-element);
|
||||
--filter-height-open: max-content;
|
||||
position: relative;
|
||||
width: var(--filter-width);
|
||||
min-width: var(--width);
|
||||
height: var(--height-close);
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
border-radius: 0.75rem;
|
||||
overflow-x: hidden;
|
||||
background-color: var(--tg-theme-button-color);
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
div[data-type="select"]>section {
|
||||
position: relative;
|
||||
height: var(--filter-height-close);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
cursor: pointer;
|
||||
background-color: var(--tg-theme-button-color);
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
div[data-type="select"]>button:has(>i.icon.close) {
|
||||
z-index: 100;
|
||||
align-self: start;
|
||||
width: var(--filter-button-width);
|
||||
height: var(--filter-height-close);
|
||||
padding: 0 0.4rem;
|
||||
}
|
||||
|
||||
div[data-type="select"]:has(>section>input[id$="title"]:checked)>button:has(>i.icon.close),
|
||||
div[data-type="select"]:focus>button:has(>i.icon.close) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div[data-type="select"]:not(>section:focus, >section:has(>button>i.icon.close)):after {
|
||||
z-index: 30;
|
||||
content: '';
|
||||
top: calc(50% - 2.5px);
|
||||
right: 1rem;
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
border-left: 5px solid transparent;
|
||||
border-top: 5px solid var(--tg-theme-button-text-color, black);
|
||||
border-right: 5px solid transparent;
|
||||
}
|
||||
|
||||
div[data-type="select"]>section>input {
|
||||
left: -99999px;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
div[data-type="select"]>section>label {
|
||||
z-index: 10;
|
||||
order: 2;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
width: calc(100% + var(--filter-button-width));
|
||||
height: var(--filter-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;
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:is([data-select="open"], :focus)>input:not(:checked)+label[for$='title'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:is([data-select="open"], :focus)>input:not(:checked)+label[for$='all']:not(:hover, :active, :focus) {
|
||||
filter: brightness(90%);
|
||||
}
|
||||
|
||||
div[data-type="select"]>section>input:not(:checked)+label {
|
||||
cursor: pointer;
|
||||
filter: brightness(80%);
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:is([data-select="open"], :focus)>input+label:hover {
|
||||
filter: brightness(110%);
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:is([data-select="open"], :focus)>input:checked+label:hover {
|
||||
filter: brightness(120%);
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:is([data-select="open"], :focus)>input+label:is(:active, :focus) {
|
||||
filter: brightness(60%);
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:is([data-select="open"], :focus)>input:checked+label:is(:active, :focus) {
|
||||
filter: brightness(70%);
|
||||
}
|
||||
|
||||
div[data-type="select"]>section>input:checked+label {
|
||||
order: 1;
|
||||
max-width: calc(var(--filter-width) - 2rem - 10px);
|
||||
display: inline;
|
||||
line-height: var(--filter-height-element);
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:is([data-select="open"], :focus)>input:checked+label {
|
||||
max-width: initial;
|
||||
padding-right: initial;
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:is([data-select="open"], :focus) {
|
||||
height: var(--filter-height-open, max-content);
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:is([data-select="open"], :focus)>label {
|
||||
position: relative;
|
||||
display: inline;
|
||||
line-height: var(--filter-height-element);
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
div[data-type="select"]>section:only-child {
|
||||
--width: 100%
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
section#loading {
|
||||
z-index: 9999;
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
backdrop-filter: brightness(50%) contrast(120%) grayscale(80%) blur(3px);
|
||||
cursor: progress;
|
||||
}
|
||||
|
||||
section#loading[disabled] {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: 0.3s ease-out;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue