From 58f065f312aa9b486a18e124d4b86417252aec9e Mon Sep 17 00:00:00 2001 From: Arsen Mirzaev Tatyano-Muradovich Date: Tue, 19 Nov 2024 04:15:30 +0300 Subject: [PATCH] BIG UPDATE + resolved #5, resolved #6, resolved #7, resolved #8, resolved #10, resolved #11, resolved #14, resolved #18, resolved #19, resolved #20 --- README.md | 145 +-- composer.json | 3 +- composer.lock | 542 ++++------- examples/arangodb/collections/settings.json | 16 + examples/arangodb/collections/suspension.json | 15 + examples/arangodb/views/products_search.json | 20 + examples/nginx/server.conf | 43 + .../systemd/huesos.service | 6 +- .../arming_bot/system/controllers/account.php | 47 +- .../arming_bot/system/controllers/cart.php | 383 +++++--- .../arming_bot/system/controllers/catalog.php | 386 ++++---- .../arming_bot/system/controllers/core.php | 92 +- .../system/controllers/deliveries/cdek.php | 78 -- .../system/controllers/delivery.php | 566 +++++++++++ .../arming_bot/system/controllers/index.php | 65 +- .../arming_bot/system/controllers/session.php | 128 ++- mirzaev/arming_bot/system/models/cart.php | 114 ++- mirzaev/arming_bot/system/models/catalog.php | 290 ++++-- mirzaev/arming_bot/system/models/core.php | 7 +- .../system/models/deliveries/cdek.php | 153 ++- .../system/models/enumerations/currency.php | 0 .../system/models/enumerations/language.php | 0 .../system/models/enumerations/session.php | 0 mirzaev/arming_bot/system/models/order.php | 43 + mirzaev/arming_bot/system/models/session.php | 12 +- mirzaev/arming_bot/system/models/telegram.php | 201 +++- .../system/models/traits/buffer.php | 14 +- .../system/models/traits/yandex/disk.php | 50 +- mirzaev/arming_bot/system/public/index.php | 80 +- .../system/public/js/authentication.js | 20 - mirzaev/arming_bot/system/public/js/core.js | 8 +- .../js/modules/{account.js => account.mjs} | 50 +- .../system/public/js/modules/cart.js | 528 ---------- .../system/public/js/modules/cart.mjs | 902 ++++++++++++++++++ .../js/modules/{catalog.js => catalog.mjs} | 383 +++++--- .../modules/{connection.js => connection.mjs} | 0 .../js/modules/{damper.js => damper.mjs} | 2 +- .../system/public/js/modules/delivery.js | 161 ---- .../system/public/js/modules/delivery.mjs | 898 +++++++++++++++++ .../system/public/js/{ => modules}/hotline.js | 0 .../js/modules/{loader.js => loader.mjs} | 11 +- .../js/modules/{session.js => session.mjs} | 40 +- .../js/modules/{telegram.js => telegram.mjs} | 0 .../arming_bot/system/public/js/telegram.js | 47 + mirzaev/arming_bot/system/public/robot.php | 35 +- mirzaev/arming_bot/system/public/socket.php | 0 .../public/themes/default/css/account.css | 0 .../system/public/themes/default/css/cart.css | 40 +- .../public/themes/default/css/catalog.css | 29 +- .../public/themes/default/css/connection.css | 0 .../public/themes/default/css/icons/close.css | 49 +- .../themes/default/css/interface/select.css | 15 +- .../system/public/themes/default/css/main.css | 102 +- .../public/themes/default/css/window.css | 43 +- mirzaev/arming_bot/system/settings/.gitignore | 1 + .../settings/deliveries/cdek.php.sample | 6 + .../system/storage/categories/.gitignore | 0 .../system/storage/import/.gitignore | 0 .../system/storage/products/.gitignore | 0 mirzaev/arming_bot/system/views/templater.php | 71 +- .../system/views/themes/default/account.html | 6 +- .../default/cart/elements/delivery.html | 16 +- .../default/cart/elements/products.html | 16 +- .../themes/default/cart/elements/summary.html | 14 +- .../views/themes/default/cart/page.html | 5 +- .../default/catalog/elements/categories.html | 7 +- .../default/catalog/elements/filters.html | 21 +- .../default/catalog/elements/products.html | 27 +- .../default/catalog/elements/search.html | 2 +- .../views/themes/default/catalog/page.html | 6 +- .../views/themes/default/connection.html | 2 +- .../system/views/themes/default/footer.html | 5 + .../system/views/themes/default/head.html | 8 +- .../themes/default/iframes/robokassa.html | 25 + .../system/views/themes/default/index.html | 4 +- .../system/views/themes/default/js.html | 9 +- .../system/views/themes/default/menu.html | 2 +- .../system/views/themes/default/offer.html | 29 + .../views/themes/default/offer/page.html | 368 +++++++ .../{orcer => order}/elements/delivery.html | 0 .../{orcer => order}/elements/summary.html | 0 .../themes/default/{orcer => order}/page.html | 4 +- 82 files changed, 5334 insertions(+), 2182 deletions(-) create mode 100644 examples/arangodb/collections/settings.json create mode 100644 examples/arangodb/collections/suspension.json create mode 100644 examples/arangodb/views/products_search.json create mode 100644 examples/nginx/server.conf rename telegram-arming_bot.service => examples/systemd/huesos.service (50%) delete mode 100755 mirzaev/arming_bot/system/controllers/deliveries/cdek.php create mode 100755 mirzaev/arming_bot/system/controllers/delivery.php mode change 100644 => 100755 mirzaev/arming_bot/system/models/enumerations/currency.php mode change 100644 => 100755 mirzaev/arming_bot/system/models/enumerations/language.php mode change 100644 => 100755 mirzaev/arming_bot/system/models/enumerations/session.php create mode 100755 mirzaev/arming_bot/system/models/order.php delete mode 100755 mirzaev/arming_bot/system/public/js/authentication.js rename mirzaev/arming_bot/system/public/js/modules/{account.js => account.mjs} (81%) delete mode 100755 mirzaev/arming_bot/system/public/js/modules/cart.js create mode 100755 mirzaev/arming_bot/system/public/js/modules/cart.mjs rename mirzaev/arming_bot/system/public/js/modules/{catalog.js => catalog.mjs} (80%) rename mirzaev/arming_bot/system/public/js/modules/{connection.js => connection.mjs} (100%) rename mirzaev/arming_bot/system/public/js/modules/{damper.js => damper.mjs} (96%) delete mode 100755 mirzaev/arming_bot/system/public/js/modules/delivery.js create mode 100755 mirzaev/arming_bot/system/public/js/modules/delivery.mjs rename mirzaev/arming_bot/system/public/js/{ => modules}/hotline.js (100%) mode change 100644 => 100755 rename mirzaev/arming_bot/system/public/js/modules/{loader.js => loader.mjs} (97%) rename mirzaev/arming_bot/system/public/js/modules/{session.js => session.mjs} (75%) rename mirzaev/arming_bot/system/public/js/modules/{telegram.js => telegram.mjs} (100%) create mode 100755 mirzaev/arming_bot/system/public/js/telegram.js mode change 100644 => 100755 mirzaev/arming_bot/system/public/socket.php mode change 100644 => 100755 mirzaev/arming_bot/system/public/themes/default/css/account.css mode change 100644 => 100755 mirzaev/arming_bot/system/public/themes/default/css/cart.css mode change 100644 => 100755 mirzaev/arming_bot/system/public/themes/default/css/catalog.css mode change 100644 => 100755 mirzaev/arming_bot/system/public/themes/default/css/connection.css create mode 100755 mirzaev/arming_bot/system/settings/deliveries/cdek.php.sample mode change 100644 => 100755 mirzaev/arming_bot/system/storage/categories/.gitignore mode change 100644 => 100755 mirzaev/arming_bot/system/storage/import/.gitignore mode change 100644 => 100755 mirzaev/arming_bot/system/storage/products/.gitignore create mode 100755 mirzaev/arming_bot/system/views/themes/default/iframes/robokassa.html mode change 100644 => 100755 mirzaev/arming_bot/system/views/themes/default/menu.html create mode 100755 mirzaev/arming_bot/system/views/themes/default/offer.html create mode 100755 mirzaev/arming_bot/system/views/themes/default/offer/page.html rename mirzaev/arming_bot/system/views/themes/default/{orcer => order}/elements/delivery.html (100%) rename mirzaev/arming_bot/system/views/themes/default/{orcer => order}/elements/summary.html (100%) rename mirzaev/arming_bot/system/views/themes/default/{orcer => order}/page.html (89%) diff --git a/README.md b/README.md index 28a568f..26c1206 100755 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # huesos - Basis for developing chat-robots with "Web App" technology for Telegram ## Installation @@ -7,36 +6,48 @@ Basis for developing chat-robots with "Web App" technology for Telegram ### AnangoDB 1. Create a Graph with the specified values -**Name:** catalog
-
-**edgeDefinition:** entry
-**fromCollections:** categoy, product
+**Name:** catalog
+
+* Relatin 1 +**edgeDefinition:** entry
+**fromCollections:** categoy, product
**toCollections:** category +* Relation 2 +**edgeDefinition:** reservation
+**fromCollections:** product
+**toCollections:** cart + 2. Create a Graph with the specified values -**Name:** sessions
-
-**edgeDefinition:** connect
-**fromCollections:** account
-**toCollections:** session +**Name:** users
+
+* Relation 1 +**edgeDefinition:** connect
+**fromCollections:** cart, session
+**toCollections:** account, session + +**Orphan Collections:** product 3. Create indexes for the "product" collection -**Type:** "Inverted Index"
-**Fields:** name.ru
-**Analyzer:** "text_ru"
-**Search field:** true
-**Name:** name_ru
-
-*Add indexes for all search parameters and for all languages (search language is selected based on the user's language,
-otherwise from the default language specified in the active settings from **settings** collection document)*
-
-*See fields in the `mirzaev/arming_bot/models/product`
+**Type:** "Inverted Index"
+**Fields:** name.ru
+**Analyzer:** "text_ru"
+**Search field:** true
+**Name:** name_ru
+
+*Add indexes for all search parameters and for all languages (search language is selected based on the user's language,
+otherwise from the default language specified in the active settings from **settings** collection document)*
+
+*See fields in the `mirzaev/arming_bot/models/product`
**name.ru**, **description.ru** and **compatibility.ru*** 4. Create a View with the specified values -**type:** search-alias (you can also use "arangosearch")
-**name:** **product**s_search
-**indexes:** +**type:** search-alias (you can also use "arangosearch")
+**name:** **product**s_search
+**indexes:**
+
+You can copy an example of view file from here: `/examples/arangodb/views/products_search.json` + ```json "indexes": [ { @@ -48,70 +59,62 @@ otherwise from the default language specified in the active settings from **sett ### NGINX -1. Example of NGINX server file -```nginx -location / { - try_files $uri $uri/ /index.php; -} +1. Create a NGINX server +You can copy an example of server file from here: `/examples/nginx/server.conf` -location ~ /(?categories|products) { - root /var/www/arming_bot/mirzaev/arming_bot/system/storage; - try_files $uri =404; -} - -location ~ \.php$ { - ... -} -``` +2. Add support for javascript modules +Edit the file `/etc/nginx/mime.types`
+`application/javascript js;` -> `application/javascript js mjs;` ### SystemD (or any alternative you like) - -1. Execute: `sudo cp telegram-huesos.service /etc/systemd/system/telegram-huesos.service` - -*before you execute the command think about **what it does** and whether the **paths** are specified correctly*
+You can copy an example of systemd file from here: `/examples/systemd/huesos.service`
+
+**Execute:** `sudo cp huesos.service /etc/systemd/system/huesos.service && sudo chmod +x /etc/systemd/system/huesos.service`
+
+*before you execute the command think about **what it does** and whether the **paths** are specified correctly*
*the configuration file is very simple and you can remake it for any alternative to SystemD that you like* + ## Settings -Settings of chat-robot and Web App
-
-Make sure you have a **settings** collection (can be created automatically) and at least one document with the "status" parameter set to "active" +Settings of chat-robot and Web App
+
+Make sure you have a **settings** collection (can be created automatically) and at least one document with the "status" parameter set to "active"
+You can copy a clean settings document without comments from here: `/examples/arangodb/collections/settings.json` + ```json { - "status": "active", - "project": { - "name": "NAME_OF_THE_PROJECT" - }, - "language": "en", - "currency": "usd" + "status": "active", + "project": { + "name": "PROJECT" + }, + "language": "en", // Will be converted to an instance of enumeration `mirzaev\arming_bot\models\enumerations\language` + "currency": "usd", // Will be converted to an instance of enumeration `mirzaev\arming_bot\models\enumerations\currency` + "company": { + "identifier": null, // Example: "000000000000000" (string|null) (if `null` it will not be displayed) + "tax": null, // Example: "000000000000" (string|null) (if `null` it will not be displayed) + "name": null, // Example: "COMPANY" (string|null) (if `null` it will not be displayed) + "offer": false, // Display the data of a public offer in the footer? (bool) (does not affect the `/offer` page generation) + "sim": null, // Examples: "+7 000 000-00-00", "70000000000" (string|null) (if `null` it will not be displayed) + "mail": null // Example: "name@domain.zone" (string|null) (if `null` it will not be displayed) + } } ``` -### Language -Language for render of interface, if account or session language is not initialized
-
-**Value:** en
-**⚠️ The value will be converted to an instance of enumeration** `mirzaev\arming_bot\models\enumerations\language` - -### Currency -Currency for calculations and render of interface, if account or session currency is not initialized
-
-**Value:** usd
-**⚠️ The value will be converted to an instance of enumeration** `mirzaev\arming_bot\models\enumerations\currency` - ## Suspensions -System of suspensions of chat-robot and Web App
-
-Make sure you have a **suspension** collection (can be created automatically) +System of suspensions of chat-robot and Web App
+
+Make sure you have a **suspension** collection (can be created automatically)
+You can copy a clean suspension document without comments from here: `/examples/arangodb/collections/suspension.json` ```json { - "end": 1726068961, + "end": 1726068961, // Unixtime "targets": { - "chat-robot": true, - "web app": true - } + "chat-robot": true, // Block chat-robot + "web app": true // Block "Web App" + }, "access": { - "tester": true, - "developer": true + "tester": true, // Account with `"tester": true` + "developer": true // Account with `"developer": true` }, "description": { "ru": "Разрабатываю каталог, поиск и корзину", diff --git a/composer.json b/composer.json index ae4b438..1351115 100755 --- a/composer.json +++ b/composer.json @@ -18,6 +18,8 @@ } ], "require": { + "php": "^8.4", + "ext-gd": "^8.4", "triagens/arangodb": "^3.8", "mirzaev/minimal": "^2.2", "mirzaev/arangodb": "^1.3", @@ -28,7 +30,6 @@ "twig/extra-bundle": "^3.7", "twig/intl-extra": "^3.10", "avadim/fast-excel-reader": "^2.19", - "openswoole/core": "22.1.5", "ttatpuot/cdek-sdk2.0": "^1.2", "guzzlehttp/guzzle": "^7.9", "php-http/guzzle7-adapter": "^1.0" diff --git a/composer.lock b/composer.lock index a3eb079..b95f836 100755 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6dd31244ca04cd9ec32a009c8ef467a7", + "content-hash": "38fcf54602711ba20c774e7ac3f568d0", "packages": [ { "name": "avadim/fast-excel-helper", - "version": "v1.2.1", + "version": "v1.2.2", "source": { "type": "git", "url": "https://github.com/aVadim483/fast-excel-helper.git", - "reference": "2792c4a20b529b537ede7148e2c29dc4677818db" + "reference": "aed67aa6e4cb516f86ebf7c3c9ef209209e70c53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aVadim483/fast-excel-helper/zipball/2792c4a20b529b537ede7148e2c29dc4677818db", - "reference": "2792c4a20b529b537ede7148e2c29dc4677818db", + "url": "https://api.github.com/repos/aVadim483/fast-excel-helper/zipball/aed67aa6e4cb516f86ebf7c3c9ef209209e70c53", + "reference": "aed67aa6e4cb516f86ebf7c3c9ef209209e70c53", "shasum": "" }, "require": { @@ -57,22 +57,22 @@ ], "support": { "issues": "https://github.com/aVadim483/fast-excel-helper/issues", - "source": "https://github.com/aVadim483/fast-excel-helper/tree/v1.2.1" + "source": "https://github.com/aVadim483/fast-excel-helper/tree/v1.2.2" }, - "time": "2024-09-06T20:37:07+00:00" + "time": "2024-10-31T17:42:37+00:00" }, { "name": "avadim/fast-excel-reader", - "version": "v2.19.0", + "version": "v2.21.1", "source": { "type": "git", "url": "https://github.com/aVadim483/fast-excel-reader.git", - "reference": "e2fa125a93c0c5115344ebab1a36b81cd2be63c8" + "reference": "53b2725954cabfa898b67e76ea36e873e870ad19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aVadim483/fast-excel-reader/zipball/e2fa125a93c0c5115344ebab1a36b81cd2be63c8", - "reference": "e2fa125a93c0c5115344ebab1a36b81cd2be63c8", + "url": "https://api.github.com/repos/aVadim483/fast-excel-reader/zipball/53b2725954cabfa898b67e76ea36e873e870ad19", + "reference": "53b2725954cabfa898b67e76ea36e873e870ad19", "shasum": "" }, "require": { @@ -114,9 +114,9 @@ ], "support": { "issues": "https://github.com/aVadim483/fast-excel-reader/issues", - "source": "https://github.com/aVadim483/fast-excel-reader/tree/v2.19.0" + "source": "https://github.com/aVadim483/fast-excel-reader/tree/v2.21.1" }, - "time": "2024-09-22T19:34:27+00:00" + "time": "2024-11-01T16:08:22+00:00" }, { "name": "badfarm/zanzara", @@ -186,7 +186,7 @@ }, { "name": "cakephp/core", - "version": "4.5.6", + "version": "4.5.7", "source": { "type": "git", "url": "https://github.com/cakephp/core.git", @@ -246,7 +246,7 @@ }, { "name": "cakephp/utility", - "version": "4.5.6", + "version": "4.5.7", "source": { "type": "git", "url": "https://github.com/cakephp/utility.git", @@ -1323,16 +1323,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v1.3.4", + "version": "v1.3.6", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81" + "reference": "f865a58ea3a0107c336b7045104c75243fa59d96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/61b87392d986dc49ad5ef64e75b1ff5fee24ef81", - "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f865a58ea3a0107c336b7045104c75243fa59d96", + "reference": "f865a58ea3a0107c336b7045104c75243fa59d96", "shasum": "" }, "require": { @@ -1380,7 +1380,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2024-08-02T07:48:17+00:00" + "time": "2024-11-11T17:06:04+00:00" }, { "name": "mirzaev/arangodb", @@ -1435,11 +1435,11 @@ }, { "name": "mirzaev/minimal", - "version": "2.2.1", + "version": "2.3.2", "source": { "type": "git", "url": "https://git.mirzaev.sexy/mirzaev/minimal", - "reference": "95ddffba30b1c63018b6afd2f540060c2cd81897" + "reference": "f95e4082ef39dcd170ad1129569e9dfb182469a1" }, "require": { "php": "~8.2" @@ -1473,20 +1473,20 @@ "docs": "https://git.mirzaev.sexy/mirzaev/minimal/wiki", "issues": "https://git.mirzaev.sexy/mirzaev/minimal/issues" }, - "time": "2024-01-03T21:08:44+00:00" + "time": "2024-10-11T07:55:40+00:00" }, { "name": "netresearch/jsonmapper", - "version": "v4.4.1", + "version": "v4.5.0", "source": { "type": "git", "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0" + "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/132c75c7dd83e45353ebb9c6c9f591952995bbf0", - "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8e76efb98ee8b6afc54687045e1b8dba55ac76e5", + "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5", "shasum": "" }, "require": { @@ -1522,22 +1522,22 @@ "support": { "email": "cweiske@cweiske.de", "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.4.1" + "source": "https://github.com/cweiske/jsonmapper/tree/v4.5.0" }, - "time": "2024-01-31T06:18:54+00:00" + "time": "2024-09-08T10:13:13+00:00" }, { "name": "nyholm/psr7", - "version": "1.8.1", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/Nyholm/psr7.git", - "reference": "aa5fc277a4f5508013d571341ade0c3886d4d00e" + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Nyholm/psr7/zipball/aa5fc277a4f5508013d571341ade0c3886d4d00e", - "reference": "aa5fc277a4f5508013d571341ade0c3886d4d00e", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", "shasum": "" }, "require": { @@ -1590,7 +1590,7 @@ ], "support": { "issues": "https://github.com/Nyholm/psr7/issues", - "source": "https://github.com/Nyholm/psr7/tree/1.8.1" + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" }, "funding": [ { @@ -1602,78 +1602,7 @@ "type": "github" } ], - "time": "2023-11-13T09:31:12+00:00" - }, - { - "name": "openswoole/core", - "version": "22.1.5", - "source": { - "type": "git", - "url": "https://github.com/openswoole/core.git", - "reference": "06dae68fdac73341ccf565ecef388434bd893141" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/openswoole/core/zipball/06dae68fdac73341ccf565ecef388434bd893141", - "reference": "06dae68fdac73341ccf565ecef388434bd893141", - "shasum": "" - }, - "require": { - "ext-openswoole": ">=22.0", - "php": ">=7.4", - "psr/http-message": "^1.0 || ^2.0", - "psr/http-server-middleware": "^1.0.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-sockets": "*", - "friendsofphp/php-cs-fixer": "^3.6", - "openswoole/ide-helper": "^22.0", - "php-http/psr7-integration-tests": "^1.1", - "phpunit/phpunit": "^9.5" - }, - "suggest": { - "ext-mysqli": "*", - "ext-pdo": "*", - "ext-redis": "Required to use redis database, and the required version is greater than or equal to 3.1.3" - }, - "type": "library", - "autoload": { - "files": [ - "src/Coroutine/functions.php" - ], - "psr-4": { - "OpenSwoole\\Core\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "OpenSwoole Group", - "email": "hello@openswoole.com" - } - ], - "description": "Openswoole core library", - "homepage": "https://openswoole.com", - "keywords": [ - "http", - "http2", - "mqtt", - "openswoole", - "php", - "tcp", - "websocket" - ], - "support": { - "docs": "https://openswoole.com/docs", - "issues": "https://github.com/openswoole/openswoole/issues", - "pull-request": "https://github.com/openswoole/openswoole/pulls", - "source": "https://github.com/openswoole/openswoole" - }, - "time": "2023-12-10T19:02:13+00:00" + "time": "2024-09-09T07:06:30+00:00" }, { "name": "opis/closure", @@ -1965,16 +1894,16 @@ }, { "name": "php-http/discovery", - "version": "1.19.4", + "version": "1.20.0", "source": { "type": "git", "url": "https://github.com/php-http/discovery.git", - "reference": "0700efda8d7526335132360167315fdab3aeb599" + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/discovery/zipball/0700efda8d7526335132360167315fdab3aeb599", - "reference": "0700efda8d7526335132360167315fdab3aeb599", + "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", "shasum": "" }, "require": { @@ -2038,9 +1967,9 @@ ], "support": { "issues": "https://github.com/php-http/discovery/issues", - "source": "https://github.com/php-http/discovery/tree/1.19.4" + "source": "https://github.com/php-http/discovery/tree/1.20.0" }, - "time": "2024-03-29T13:00:05+00:00" + "time": "2024-10-02T11:20:13+00:00" }, { "name": "php-http/guzzle7-adapter", @@ -2623,119 +2552,6 @@ }, "time": "2023-04-04T09:50:52+00:00" }, - { - "name": "psr/http-server-handler", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4", - "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0 || ^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side request handler", - "keywords": [ - "handler", - "http", - "http-interop", - "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" - ], - "support": { - "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2" - }, - "time": "2023-04-10T20:06:20+00:00" - }, - { - "name": "psr/http-server-middleware", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-server-middleware.git", - "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829", - "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "psr/http-message": "^1.0 || ^2.0", - "psr/http-server-handler": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP server-side middleware", - "keywords": [ - "http", - "http-interop", - "middleware", - "psr", - "psr-15", - "psr-7", - "request", - "response" - ], - "support": { - "issues": "https://github.com/php-fig/http-server-middleware/issues", - "source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2" - }, - "time": "2023-04-11T06:14:47+00:00" - }, { "name": "psr/log", "version": "1.1.4", @@ -3778,16 +3594,16 @@ }, { "name": "symfony/cache", - "version": "v7.1.4", + "version": "v7.1.7", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "b61e464d7687bb7e8f677d5031c632bf3820df18" + "reference": "23b61c9592ee72233c31625f0ae805dd1571e928" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/b61e464d7687bb7e8f677d5031c632bf3820df18", - "reference": "b61e464d7687bb7e8f677d5031c632bf3820df18", + "url": "https://api.github.com/repos/symfony/cache/zipball/23b61c9592ee72233c31625f0ae805dd1571e928", + "reference": "23b61c9592ee72233c31625f0ae805dd1571e928", "shasum": "" }, "require": { @@ -3855,7 +3671,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.1.4" + "source": "https://github.com/symfony/cache/tree/v7.1.7" }, "funding": [ { @@ -3871,7 +3687,7 @@ "type": "tidelift" } ], - "time": "2024-08-12T09:59:40+00:00" + "time": "2024-11-05T15:34:55+00:00" }, { "name": "symfony/cache-contracts", @@ -3951,16 +3767,16 @@ }, { "name": "symfony/config", - "version": "v7.1.1", + "version": "v7.1.7", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "2210fc99fa42a259eb6c89d1f724ce0c4d62d5d2" + "reference": "dc373a5cbd345354696f5dfd39c5c7a8ea23f4c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/2210fc99fa42a259eb6c89d1f724ce0c4d62d5d2", - "reference": "2210fc99fa42a259eb6c89d1f724ce0c4d62d5d2", + "url": "https://api.github.com/repos/symfony/config/zipball/dc373a5cbd345354696f5dfd39c5c7a8ea23f4c8", + "reference": "dc373a5cbd345354696f5dfd39c5c7a8ea23f4c8", "shasum": "" }, "require": { @@ -4006,7 +3822,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.1.1" + "source": "https://github.com/symfony/config/tree/v7.1.7" }, "funding": [ { @@ -4022,20 +3838,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-11-04T11:34:07+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.1.4", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "5320e0bc2c9e2d7450bb4091e497a305a68b28ed" + "reference": "e4d13f0f394f4d02a041ff76acd31c5a20a5f70b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/5320e0bc2c9e2d7450bb4091e497a305a68b28ed", - "reference": "5320e0bc2c9e2d7450bb4091e497a305a68b28ed", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e4d13f0f394f4d02a041ff76acd31c5a20a5f70b", + "reference": "e4d13f0f394f4d02a041ff76acd31c5a20a5f70b", "shasum": "" }, "require": { @@ -4086,7 +3902,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.1.4" + "source": "https://github.com/symfony/dependency-injection/tree/v7.1.8" }, "funding": [ { @@ -4102,7 +3918,7 @@ "type": "tidelift" } ], - "time": "2024-08-29T08:16:25+00:00" + "time": "2024-11-09T09:16:45+00:00" }, { "name": "symfony/deprecation-contracts", @@ -4173,16 +3989,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.1.3", + "version": "v7.1.7", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "432bb369952795c61ca1def65e078c4a80dad13c" + "reference": "010e44661f4c6babaf8c4862fe68c24a53903342" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/432bb369952795c61ca1def65e078c4a80dad13c", - "reference": "432bb369952795c61ca1def65e078c4a80dad13c", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/010e44661f4c6babaf8c4862fe68c24a53903342", + "reference": "010e44661f4c6babaf8c4862fe68c24a53903342", "shasum": "" }, "require": { @@ -4228,7 +4044,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.1.3" + "source": "https://github.com/symfony/error-handler/tree/v7.1.7" }, "funding": [ { @@ -4244,20 +4060,20 @@ "type": "tidelift" } ], - "time": "2024-07-26T13:02:51+00:00" + "time": "2024-11-05T15:34:55+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.1.1", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7" + "reference": "87254c78dd50721cfd015b62277a8281c5589702" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", - "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/87254c78dd50721cfd015b62277a8281c5589702", + "reference": "87254c78dd50721cfd015b62277a8281c5589702", "shasum": "" }, "require": { @@ -4308,7 +4124,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.6" }, "funding": [ { @@ -4324,7 +4140,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -4404,16 +4220,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.1.2", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "92a91985250c251de9b947a14bb2c9390b1a562c" + "reference": "c835867b3c62bb05c7fe3d637c871c7ae52024d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/92a91985250c251de9b947a14bb2c9390b1a562c", - "reference": "92a91985250c251de9b947a14bb2c9390b1a562c", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/c835867b3c62bb05c7fe3d637c871c7ae52024d4", + "reference": "c835867b3c62bb05c7fe3d637c871c7ae52024d4", "shasum": "" }, "require": { @@ -4450,7 +4266,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.1.2" + "source": "https://github.com/symfony/filesystem/tree/v7.1.6" }, "funding": [ { @@ -4466,20 +4282,20 @@ "type": "tidelift" } ], - "time": "2024-06-28T10:03:55+00:00" + "time": "2024-10-25T15:11:02+00:00" }, { "name": "symfony/finder", - "version": "v7.1.4", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "d95bbf319f7d052082fb7af147e0f835a695e823" + "reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/d95bbf319f7d052082fb7af147e0f835a695e823", - "reference": "d95bbf319f7d052082fb7af147e0f835a695e823", + "url": "https://api.github.com/repos/symfony/finder/zipball/2cb89664897be33f78c65d3d2845954c8d7a43b8", + "reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8", "shasum": "" }, "require": { @@ -4514,7 +4330,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.1.4" + "source": "https://github.com/symfony/finder/tree/v7.1.6" }, "funding": [ { @@ -4530,20 +4346,20 @@ "type": "tidelift" } ], - "time": "2024-08-13T14:28:19+00:00" + "time": "2024-10-01T08:31:23+00:00" }, { "name": "symfony/framework-bundle", - "version": "v7.1.4", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "711af4eefcb4054a9c93e44b403626e1826bcddd" + "reference": "1d616d762905091e798d64c53ffe3840ccfc3d89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/711af4eefcb4054a9c93e44b403626e1826bcddd", - "reference": "711af4eefcb4054a9c93e44b403626e1826bcddd", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/1d616d762905091e798d64c53ffe3840ccfc3d89", + "reference": "1d616d762905091e798d64c53ffe3840ccfc3d89", "shasum": "" }, "require": { @@ -4552,7 +4368,7 @@ "php": ">=8.2", "symfony/cache": "^6.4|^7.0", "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^7.1", + "symfony/dependency-injection": "^7.1.5", "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^6.4|^7.0", "symfony/event-dispatcher": "^6.4|^7.0", @@ -4581,6 +4397,7 @@ "symfony/mime": "<6.4", "symfony/property-access": "<6.4", "symfony/property-info": "<6.4", + "symfony/runtime": "<6.4.13|>=7.0,<7.1.6", "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", "symfony/security-core": "<6.4", "symfony/security-csrf": "<6.4", @@ -4661,7 +4478,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v7.1.4" + "source": "https://github.com/symfony/framework-bundle/tree/v7.1.6" }, "funding": [ { @@ -4677,20 +4494,20 @@ "type": "tidelift" } ], - "time": "2024-08-11T16:10:02+00:00" + "time": "2024-10-25T15:11:02+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.1.3", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a" + "reference": "f4419ec69ccfc3f725a4de7c20e4e57626d10112" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f602d5c17d1fa02f8019ace2687d9d136b7f4a1a", - "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f4419ec69ccfc3f725a4de7c20e4e57626d10112", + "reference": "f4419ec69ccfc3f725a4de7c20e4e57626d10112", "shasum": "" }, "require": { @@ -4700,12 +4517,12 @@ }, "conflict": { "doctrine/dbal": "<3.6", - "symfony/cache": "<6.4" + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" }, "require-dev": { "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.4|^7.0", + "symfony/cache": "^6.4.12|^7.1.5", "symfony/dependency-injection": "^6.4|^7.0", "symfony/expression-language": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", @@ -4738,7 +4555,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.1.3" + "source": "https://github.com/symfony/http-foundation/tree/v7.1.8" }, "funding": [ { @@ -4754,20 +4571,20 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:41:01+00:00" + "time": "2024-11-09T09:16:45+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.1.4", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "6efcbd1b3f444f631c386504fc83eeca25963747" + "reference": "33fef24e3dc79d6d30bf4936531f2f4bd2ca189e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6efcbd1b3f444f631c386504fc83eeca25963747", - "reference": "6efcbd1b3f444f631c386504fc83eeca25963747", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/33fef24e3dc79d6d30bf4936531f2f4bd2ca189e", + "reference": "33fef24e3dc79d6d30bf4936531f2f4bd2ca189e", "shasum": "" }, "require": { @@ -4852,7 +4669,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.1.4" + "source": "https://github.com/symfony/http-kernel/tree/v7.1.8" }, "funding": [ { @@ -4868,20 +4685,20 @@ "type": "tidelift" } ], - "time": "2024-08-30T17:02:28+00:00" + "time": "2024-11-13T14:25:32+00:00" }, { "name": "symfony/intl", - "version": "v7.1.1", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "66c1ecda092b1130ada2cf5f59dacfd5b6e9c99c" + "reference": "e56b243fc0afa5a12bd11dace4002ada5a7d99f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/66c1ecda092b1130ada2cf5f59dacfd5b6e9c99c", - "reference": "66c1ecda092b1130ada2cf5f59dacfd5b6e9c99c", + "url": "https://api.github.com/repos/symfony/intl/zipball/e56b243fc0afa5a12bd11dace4002ada5a7d99f8", + "reference": "e56b243fc0afa5a12bd11dace4002ada5a7d99f8", "shasum": "" }, "require": { @@ -4938,7 +4755,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v7.1.1" + "source": "https://github.com/symfony/intl/tree/v7.1.8" }, "funding": [ { @@ -4954,24 +4771,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-11-08T15:46:42+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -5017,7 +4834,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -5033,24 +4850,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -5097,7 +4914,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -5113,24 +4930,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -5173,7 +4990,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" }, "funding": [ { @@ -5189,24 +5006,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9" + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", - "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -5249,7 +5066,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" }, "funding": [ { @@ -5265,20 +5082,20 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:35:24+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/routing", - "version": "v7.1.4", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "1500aee0094a3ce1c92626ed8cf3c2037e86f5a7" + "reference": "66a2c469f6c22d08603235c46a20007c0701ea0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/1500aee0094a3ce1c92626ed8cf3c2037e86f5a7", - "reference": "1500aee0094a3ce1c92626ed8cf3c2037e86f5a7", + "url": "https://api.github.com/repos/symfony/routing/zipball/66a2c469f6c22d08603235c46a20007c0701ea0a", + "reference": "66a2c469f6c22d08603235c46a20007c0701ea0a", "shasum": "" }, "require": { @@ -5330,7 +5147,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.1.4" + "source": "https://github.com/symfony/routing/tree/v7.1.6" }, "funding": [ { @@ -5346,7 +5163,7 @@ "type": "tidelift" } ], - "time": "2024-08-29T08:16:25+00:00" + "time": "2024-10-01T08:31:23+00:00" }, { "name": "symfony/service-contracts", @@ -5511,16 +5328,16 @@ }, { "name": "symfony/twig-bridge", - "version": "v7.1.4", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "2db32cfe8fc57797908ef0bee232b90dbe42af66" + "reference": "535ab0be4fc563b2bc5fc0cc9e388626d226c63f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/2db32cfe8fc57797908ef0bee232b90dbe42af66", - "reference": "2db32cfe8fc57797908ef0bee232b90dbe42af66", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/535ab0be4fc563b2bc5fc0cc9e388626d226c63f", + "reference": "535ab0be4fc563b2bc5fc0cc9e388626d226c63f", "shasum": "" }, "require": { @@ -5600,7 +5417,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v7.1.4" + "source": "https://github.com/symfony/twig-bridge/tree/v7.1.8" }, "funding": [ { @@ -5616,20 +5433,20 @@ "type": "tidelift" } ], - "time": "2024-08-29T08:16:25+00:00" + "time": "2024-11-10T02:47:09+00:00" }, { "name": "symfony/twig-bundle", - "version": "v7.1.1", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "d48c2f08c2f315e749f0e18fc4945b7be8afe1e5" + "reference": "af902314a71fb412ae412094f7e1d7e49594507b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/d48c2f08c2f315e749f0e18fc4945b7be8afe1e5", - "reference": "d48c2f08c2f315e749f0e18fc4945b7be8afe1e5", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/af902314a71fb412ae412094f7e1d7e49594507b", + "reference": "af902314a71fb412ae412094f7e1d7e49594507b", "shasum": "" }, "require": { @@ -5684,7 +5501,7 @@ "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bundle/tree/v7.1.1" + "source": "https://github.com/symfony/twig-bundle/tree/v7.1.6" }, "funding": [ { @@ -5700,20 +5517,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.1.4", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "a5fa7481b199090964d6fd5dab6294d5a870c7aa" + "reference": "7bb01a47b1b00428d32b5e7b4d3b2d1aa58d3db8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/a5fa7481b199090964d6fd5dab6294d5a870c7aa", - "reference": "a5fa7481b199090964d6fd5dab6294d5a870c7aa", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7bb01a47b1b00428d32b5e7b4d3b2d1aa58d3db8", + "reference": "7bb01a47b1b00428d32b5e7b4d3b2d1aa58d3db8", "shasum": "" }, "require": { @@ -5767,7 +5584,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.1.4" + "source": "https://github.com/symfony/var-dumper/tree/v7.1.8" }, "funding": [ { @@ -5783,20 +5600,20 @@ "type": "tidelift" } ], - "time": "2024-08-30T16:12:47+00:00" + "time": "2024-11-08T15:46:42+00:00" }, { "name": "symfony/var-exporter", - "version": "v7.1.2", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "b80a669a2264609f07f1667f891dbfca25eba44c" + "reference": "90173ef89c40e7c8c616653241048705f84130ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/b80a669a2264609f07f1667f891dbfca25eba44c", - "reference": "b80a669a2264609f07f1667f891dbfca25eba44c", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/90173ef89c40e7c8c616653241048705f84130ef", + "reference": "90173ef89c40e7c8c616653241048705f84130ef", "shasum": "" }, "require": { @@ -5843,7 +5660,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.1.2" + "source": "https://github.com/symfony/var-exporter/tree/v7.1.6" }, "funding": [ { @@ -5859,7 +5676,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T08:00:31+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "thecodingmachine/safe", @@ -6331,16 +6148,16 @@ }, { "name": "twig/twig", - "version": "v3.13.0", + "version": "v3.14.2", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "afc0eb63dc66c248c5a94504dc2b255bc9b86575" + "reference": "0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/afc0eb63dc66c248c5a94504dc2b255bc9b86575", - "reference": "afc0eb63dc66c248c5a94504dc2b255bc9b86575", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a", + "reference": "0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a", "shasum": "" }, "require": { @@ -6394,7 +6211,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.13.0" + "source": "https://github.com/twigphp/Twig/tree/v3.14.2" }, "funding": [ { @@ -6406,7 +6223,7 @@ "type": "tidelift" } ], - "time": "2024-09-07T08:01:12+00:00" + "time": "2024-11-07T12:36:22+00:00" }, { "name": "wyrihaximus/composer-update-bin-autoload-path", @@ -6989,10 +6806,13 @@ "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [], + "platform": { + "php": "^8.4", + "ext-gd": "^8.4" + }, + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/examples/arangodb/collections/settings.json b/examples/arangodb/collections/settings.json new file mode 100644 index 0000000..4d0eeb6 --- /dev/null +++ b/examples/arangodb/collections/settings.json @@ -0,0 +1,16 @@ +{ + "status": "active", + "project": { + "name": "PROJECT" + }, + "language": "en", + "currency": "usd", + "company": { + "identifier": null, + "tax": null, + "name": null, + "offer": false, + "sim": null, + "mail": null + } +} diff --git a/examples/arangodb/collections/suspension.json b/examples/arangodb/collections/suspension.json new file mode 100644 index 0000000..a2ae68c --- /dev/null +++ b/examples/arangodb/collections/suspension.json @@ -0,0 +1,15 @@ +{ + "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" + } +} diff --git a/examples/arangodb/views/products_search.json b/examples/arangodb/views/products_search.json new file mode 100644 index 0000000..224ef91 --- /dev/null +++ b/examples/arangodb/views/products_search.json @@ -0,0 +1,20 @@ +{ + "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" +} diff --git a/examples/nginx/server.conf b/examples/nginx/server.conf new file mode 100644 index 0000000..0bc9f48 --- /dev/null +++ b/examples/nginx/server.conf @@ -0,0 +1,43 @@ +server { + listen 443 ssl; + listen [::]:443 ssl ipv6only=on; + + server_name domain.zone; + + root /var/www/project/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; + } + + location ~ /(?categories|products) { + root /var/www/arming_bot/mirzaev/arming_bot/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; +} + diff --git a/telegram-arming_bot.service b/examples/systemd/huesos.service similarity index 50% rename from telegram-arming_bot.service rename to examples/systemd/huesos.service index 8a4035e..05f7e1b 100755 --- a/telegram-arming_bot.service +++ b/examples/systemd/huesos.service @@ -1,12 +1,12 @@ [Unit] -Description=Telegram-robot - @arming_bot +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/arming_bot/mirzaev/arming_bot/system/public/robot.php -PIDFile=/var/run/php/telegram-arming_bot.pid +ExecStart=sudo -u www-data /usr/bin/php /var/www/project/mirzaev/huesos/system/public/robot.php +PIDFile=/var/run/php/huesos.pid RemainAfterExit=no RuntimeMaxSec=3600s Restart=always diff --git a/mirzaev/arming_bot/system/controllers/account.php b/mirzaev/arming_bot/system/controllers/account.php index e0e98ad..4106f59 100755 --- a/mirzaev/arming_bot/system/controllers/account.php +++ b/mirzaev/arming_bot/system/controllers/account.php @@ -5,42 +5,49 @@ declare(strict_types=1); namespace mirzaev\arming_bot\controllers; // Files of the project -use mirzaev\arming_bot\controllers\core, - mirzaev\arming_bot\models\session, - mirzaev\arming_bot\models\account as model; +use mirzaev\arming_bot\controllers\core; -// Framework for ArangoDB -use mirzaev\arangodb\document; +// Framework for PHP +use mirzaev\minimal\http\enumerations\status; /** * Controller of account * * @package mirzaev\arming_bot\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 */ final class account extends core { /** - * Registry of errors + * Errors + * + * @var array $errors Registry of errors */ protected array $errors = [ 'session' => [], - 'account' => [] + 'account' => [], + 'buffer' => [] ]; /** + * Write + * * Write to the buffer * - * @param array $parameters Parameters of the request (POST + GET) + * @param mixed ...$parameters Parameters for writing to the buffer * - * @return void + * @return null + * + * @todo переделать под trait buffer */ - public function write(array $parameters = []): void + public function write(mixed ...$parameters): null { - if (!empty($parameters) && $this->account instanceof model) { - // Received parameters and initialized account + if (!empty($parameters) && isset($this->account)) { + // Received parameters and initialized model with buffer trait // Declaring the buffer of deserialized parameters $deserialized = []; @@ -49,22 +56,28 @@ final class account extends core // Iterate over parameters // Validation of the parameter value - if (mb_strlen($value) > 4096) continue; + if (mb_strlen(serialize($value)) > 4096) continue; // Declaring the buffer of deserialized parameter $parameter = null; // Deserializing name to multidimensional array - foreach (array_reverse(explode('_', $name)) as $key) - $parameter = [$key => $parameter ?? (json_validate($value) ? json_decode($value, true, 10) : $value)]; + 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']); - // Write to the account document from ArangoDB - if (!empty($deserialized)) $this->account->write($deserialized, $this->errors['account']); + // Writing status of response + $this->response->status = status::created; } + + // Exit (success) + return null; } + } diff --git a/mirzaev/arming_bot/system/controllers/cart.php b/mirzaev/arming_bot/system/controllers/cart.php index d0b9453..d955fd5 100755 --- a/mirzaev/arming_bot/system/controllers/cart.php +++ b/mirzaev/arming_bot/system/controllers/cart.php @@ -11,40 +11,54 @@ use mirzaev\arming_bot\controllers\core, mirzaev\arming_bot\models\menu, mirzaev\arming_bot\models\enumerations\language; -// Framework for ArangoDB -use mirzaev\arangodb\document; +// Framework for PHP +use mirzaev\minimal\http\enumerations\content, + mirzaev\minimal\http\enumerations\status; /** * Controller of cart * * @package mirzaev\arming_bot\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 */ final class cart extends core { /** - * Instance of the cart + * Cart + * + * @var model|null $cart Instance of the cart */ protected readonly ?model $cart; /** - * Registry of errors + * Errors + * + * @var array $errors Registry of errors */ protected array $errors = [ 'session' => [], 'account' => [], - 'cart' => [], 'menu' => [], + 'cart' => [] ]; /** - * Cart + * Index * - * @param array $parameters Parameters of the request (POST + GET) + * HTML-document with shopping cart and delivery settings + * + * @param null */ - public function index(array $parameters = []): ?string + public function index(): null { if (isset($menu)) { // @@ -63,7 +77,7 @@ final class cart extends core } // Initializing the cart - $this->cart = $this->account?->cart() ?? $this->session?->cart(); + $this->cart ??= $this->account?->cart() ?? $this->session?->cart(); // Initializing the cart data $this->view->cart = [ @@ -78,68 +92,75 @@ final class cart extends core ] ]; - if ($_SERVER['REQUEST_METHOD'] === 'GET') { - // GET request + if (str_contains($this->request->headers['accept'], content::json->value)) { + // Request for JSON response - // Exit (success) - return $this->view->render('cart/page.html', [ - 'h2' => $this->language === language::ru ? 'Корзина' : 'Cart' // @see https://git.mirzaev.sexy/mirzaev/huesos/issues/1 - ]); - } else if ($_SERVER['REQUEST_METHOD'] === 'POST') { - // POST request - - // Initializing a response headers - header('Content-Type: application/json'); - header('Content-Encoding: none'); - header('X-Accel-Buffering: no'); - - // Initializing of the output buffer - ob_start(); - - // Generating the reponse - echo json_encode( - [ + // 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 - // Initializing a response headers - header('Content-Length: ' . ob_get_length()); + // 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 and deinitializing of the output buffer - ob_end_flush(); - flush(); + // Sending response + $this->response + ->start() + ->clean() + ->sse() + ->write($page) + ->validate($this->request) + ?->body() + ->end(); - // Exit (success) - return null; + // Deinitializing rendered page + unset($page); } - // Exit (fail) + // Exit (success/fail) return null; } /** * Product * - * Write or delete the product in the cart + * Change status of the product in the cart * - * @param array $parameters Parameters of the request (POST + GET) + * @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(array $parameters = []): ?string - { - if ($_SERVER['REQUEST_METHOD'] === 'POST') { - // POST request + 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 of the buffer with amount of the product in the cart - $amount = 0; + // Declaring buffer with amount of the product in the cart + $cart = 0; // Validating @todo add throwing errors - $identifier = null; - if (isset($parameters['identifier']) && preg_match('/[\d]+/', urldecode($parameters['identifier']), $matches)) $identifier = (int) $matches[0]; + if (isset($identifier) && preg_match('/[\d]+/', urldecode($identifier), $matches)) $identifier = (int) $matches[0]; + else unset($identifier); if (isset($identifier)) { // Received and validated identfier of the product @@ -157,20 +178,20 @@ final class cart extends core // Initialized the product // Initializing the cart - $this->cart = $this->account?->cart() ?? $this->session?->cart(); + $this->cart ??= $this->account?->cart() ?? $this->session?->cart(); if ($this->cart instanceof model) { // Initialized the cart - // Initializing the buffer with amount of the product in the cart - $amount = $this->cart->count(product: $product, limit: 100, errors: $this->errors['cart']) ?? 0; + // 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 - $type = null; - if (isset($parameters['type']) && preg_match('/[\w]+/', urldecode($parameters['type']), $matches)) $type = $matches[0]; + 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 @@ -178,14 +199,14 @@ final class cart extends core if ($type === 'toggle') { // Write the product to the cart if is not in the cart and vice versa - if ($amount > 0) { + if ($cart > 0) { // The cart has the product // Deleting the product from the cart - $this->cart->delete(product: $product, amount: $amount, errors: $this->errors['cart']); + $this->cart->delete(product: $product, amount: $cart, errors: $this->errors['cart']); // Reinitializing the buffer with amount of the product in the cart - $amount = 0; + $cart = 0; } else { // The cart has no the product @@ -193,62 +214,62 @@ final class cart extends core $this->cart->write(product: $product, amount: 1, errors: $this->errors['cart']); // Reinitializing the buffer with amount of the product in the cart - $amount = 1; + $cart = 1; } } else { // Received not the "toggle" command // Validating @todo add throwing errors - $_amount = null; - if (isset($parameters['amount']) && preg_match('/[\d]+/', urldecode($parameters['amount']), $matches)) $_amount = (int) $matches[0]; + if (isset($amount) && preg_match('/[\d]+/', urldecode($amount), $matches)) $amount = (int) $matches[0]; + else unset($amount); - if (isset($_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 ($amount + $_amount < 101) { + 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']); + $this->cart->write(product: $product, amount: $amount, errors: $this->errors['cart']); // Reinitialize the buffer with amount of the product in the cart - $amount += $_amount; + $cart += $amount; } } else if ($type === 'delete') { // Decrease amount of the product in the cart - if ($amount - $_amount > -1) { + 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']); + $this->cart->delete(product: $product, amount: $amount, errors: $this->errors['cart']); // Reinitialize the buffer with amount of the product in the cart - $amount -= $_amount; + $cart -= $amount; } } else if ($type === 'set') { // Set amount of the product in the cart - if ($_amount > -1 && $_amount < 101) { + if ($amount > -1 && $amount < 101) { // Validated amount to setting - if ($_amount > $amount) { + if ($amount > $cart) { // Requested amount more than actual amount of the product in the cart - // Writing the product from the cart - $this->cart->write(product: $product, amount: $_amount - $amount, errors: $this->errors['cart']); + // 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: $amount - $_amount, errors: $this->errors['cart']); + $this->cart->delete(product: $product, amount: $cart - $amount, errors: $this->errors['cart']); } // Reinitializing the buffer with amount of the product in the cart - $amount = $_amount; + $cart = $amount; } } } @@ -259,49 +280,41 @@ final class cart extends core } } - // Initializing a response headers - header('Content-Type: application/json'); - header('Content-Encoding: none'); - header('X-Accel-Buffering: no'); - - // Initializing of the output buffer - ob_start(); - - // Generating the reponse - echo json_encode( - [ - 'amount' => $amount, // $amount does not store a real value, but is calculated without a repeated request to ArangoDB + // 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(); - // Initializing a response headers - header('Content-Length: ' . ob_get_length()); - - // Sending and deinitializing of the output buffer - ob_end_flush(); - flush(); - - // Exit (success) - return null; + // Deinitializing buffer with amount of the product in the cart + unset($cart); } - // Exit (fail) + // Exit (success/fail) return null; } /** * Summary * - * @param array $parameters Parameters of the request (POST + GET) + * Information about products in the cart + * + * @return null */ - public function summary(array $parameters = []): ?string + public function summary(): null { - if ($_SERVER['REQUEST_METHOD'] === 'POST') { - // POST request + 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(); + $this->cart ??= $this->account?->cart() ?? $this->session?->cart(); if ($this->cart instanceof model) { // Initialized the cart @@ -309,36 +322,156 @@ final class cart extends core // Initializing summary data of the cart $summary = $this->cart?->summary(currency: $this->currency, errors: $this->errors['cart']); - // Initializing response headers - header('Content-Type: application/json'); - header('Content-Encoding: none'); - header('X-Accel-Buffering: no'); - - // Initializing of the output buffer - ob_start(); - - // Generating the reponse - echo json_encode( - [ + // Sending response + $this->response + ->start() + ->clean() + ->sse() + ->json([ 'cost' => $summary['cost'] ?? 0, 'amount' => $summary['amount'] ?? 0, 'errors' => $this->errors - ] - ); + ]) + ->validate($this->request) + ?->body() + ->end(); - // Initializing a response headers - header('Content-Length: ' . ob_get_length()); - - // Sending and deinitializing of the output buffer - ob_end_flush(); - flush(); - - // Exit (success) - return null; + // Deinitializing summary data of the cart + unset($summary); } } - // Exit (fail) + // 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; } } diff --git a/mirzaev/arming_bot/system/controllers/catalog.php b/mirzaev/arming_bot/system/controllers/catalog.php index 1b06b95..1a7aa8b 100755 --- a/mirzaev/arming_bot/system/controllers/catalog.php +++ b/mirzaev/arming_bot/system/controllers/catalog.php @@ -10,8 +10,14 @@ use mirzaev\arming_bot\controllers\core, mirzaev\arming_bot\models\entry, mirzaev\arming_bot\models\category, mirzaev\arming_bot\models\product, + mirzaev\arming_bot\models\cart, mirzaev\arming_bot\models\menu; +// 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; @@ -20,13 +26,27 @@ use ArangoDBClient\Document as _document; * * @package mirzaev\arming_bot\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 */ final class catalog extends core { /** - * Registry of errors + * Cart + * + * @var model|null $cart Instance of the cart + */ + protected readonly ?cart $cart; + + /** + * Errors + * + * @var array $errors Registry of errors */ protected array $errors = [ 'session' => [], @@ -38,203 +58,124 @@ final class catalog extends core /** * Catalog * - * @param array $parameters Parameters of the request (POST + GET) + * 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(array $parameters = []): ?string - { - // validating - if (isset($parameters['product']) && preg_match('/[\d]+/', $parameters['product'], $matches)) $product = (int) $matches[0]; + 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 the cart data + $this->view->cart = [ + 'products' => $this->cart?->products(language: $this->language, currency: $this->currency) + ]; + + // 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 identifier of the 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: '{identifier: d.identifier, name: d.name.@language, description: d.description.@language, cost: d.cost.@currency, weight: d.weight, dimensions: d.dimensions, brand: d.brand.@language, compatibility: d.compatibility.@language, cost: d.cost.@currency, images: d.images[*].storage}', + 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'] - )?->getAll(); + ); + + // 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' => [ + '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 = []; + $parameters = []; // Initializing buffer of filters query (AQL) $_filters = 'd.deleted != true && d.hidden != true'; - // Validating - if (isset($parameters['brand']) && preg_match('/[\w\s]+/u', urldecode($parameters['brand']), $matches)) $brand = $matches[0]; + // 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 account buffer - $this->account?->write( - [ - 'catalog' => [ - 'filters' => [ - 'brand' => $brand - ] - ] - ] - ); - - // Writing to the session buffer - $this->session?->write( - [ - 'catalog' => [ - 'filters' => [ - 'brand' => $brand - ] - ] - ] - ); - // Writing to the buffer of filters query (AQL) $_filters .= ' && d.brand.@language == @brand'; // Writing to the buffer of query parameters - $_parameters['brand'] = $brand; - } else { - // Not received or not validated filter by brand - - // Writing to the account buffer - $this->account?->write( - [ - 'catalog' => [ - 'filters' => [ - 'brand' => null - ] - ] - ] - ); - - // Writing to the session buffer - $this->session?->write( - [ - 'catalog' => [ - 'filters' => [ - 'brand' => null - ] - ] - ] - ); + $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 - if (isset($parameters['sort']) && preg_match('/[\w\s]+/u', $parameters['sort'], $matches)) $sort = $matches[0]; + // 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 - // Writing to the account buffer - $this->account?->write( - [ - 'catalog' => [ - 'sort' => $sort - ] - ] - ); - - // Writing to the session buffer - $this->session?->write( - [ - 'catalog' => [ - 'sort' => $sort - ] - ] - ); - // Write to the buffer of sort query (AQL) $_sort = "d.@sort DESC, $_sort"; // Write to the buffer of query parameters - $_parameters['sort'] = $sort; - } else { - // Not received or not validated filter by brand - - // Writing to the account buffer - $this->account?->write( - [ - 'catalog' => [ - 'sort' => null - ] - ] - ); - - // Writing to the session buffer - $this->session?->write( - [ - 'catalog' => [ - 'sort' => null - ] - ] - ); + $parameters['sort'] = $sort; } - // Validating @todo add throwing errors - if (isset($parameters['text']) && preg_match('/[\w\s]+/u', urldecode($parameters['text']), $matches) && mb_strlen($matches[0]) > 2) $text = $matches[0]; + // Writing to the session buffer + $this->core->request(new request('PATCH', '/session/write', protocol::http_3, parameters: ['catalog_sort' => $sort ?? null])); - if (isset($text)) { - // Received and validated text - - // Writing to the account buffer (useless becouse rewrite itself to null with every request) - $this->account?->write( - [ - 'catalog' => [ - 'search' => [ - 'text' => $text - ] - ] - ] - ); - - // Writing to the session buffer (useless becouse rewrite itself to null with every request) - $this->session?->write( - [ - 'catalog' => [ - 'search' => [ - 'text' => $text - ] - ] - ] - ); - } else { - // Not received or not validated filter by brand - - // Writing to the account buffer - $this->account?->write( - [ - 'catalog' => [ - 'search' => [ - 'text' => null - ] - ] - ] - ); - - // Writing to the session buffer - $this->session?->write( - [ - 'catalog' => [ - 'search' => [ - 'text' => null - ] - ] - ] - ); - } + // Writing to the account buffer + $this->core->request(new request('PATCH', '/account/write', protocol::http_3, parameters: ['catalog_sort' => $sort ?? null])); // Validating - if (isset($parameters['category']) && preg_match('/[\d]+/', $parameters['category'], $matches)) $category = (int) $matches[0]; + 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 @@ -284,7 +225,7 @@ final class catalog extends core // Deleting buffers unset($category, $product); } - } else if (!isset($parameters['category'])) { + } else if (!isset($category)) { // Not received identifier of the category // search for root ascendants categories @@ -296,6 +237,16 @@ final class catalog extends core ) ?? null; } + // 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 @@ -323,7 +274,7 @@ final class catalog extends core 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, + parameters: $parameters, errors: $this->errors['catalog'] ); } @@ -344,8 +295,56 @@ final class catalog extends core ); } - if ($_SERVER['REQUEST_METHOD'] === 'GET') { - // GET request + 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 @@ -363,73 +362,26 @@ final class catalog extends core ] + ($this->view->javascript ?? []); } - // Exit (success) - return $this->view->render('catalog/page.html', [ + // Render page + $page = $this->view->render('catalog/page.html', [ 'h2' => $this->language === language::ru ? 'Каталог' : 'Catalog' // @see https://git.mirzaev.sexy/mirzaev/huesos/issues/1 ]); - } else if ($_SERVER['REQUEST_METHOD'] === 'POST') { - // POST request - // Initializing the buffer of response - $response = [ - 'title' => $this->language === language::ru ? 'Каталог' : 'Catalog' // @see https://git.mirzaev.sexy/mirzaev/huesos/issues/1 - ]; + // Sending response + $this->response + ->start() + ->clean() + ->sse() + ->write($page) + ->validate($this->request) + ?->body() + ->end(); - if (isset($this->view->categories)) { - // Initialized categories - - // Render HTML-code of categories and write to the response buffer - $response['categories'] = $this->view->render('catalog/elements/categories.html'); - } - - if (isset($this->view->product)) { - // Initialized product - - // Writing data of the product to the response buffer @todo GENERATE THIS ON THE SERVER - $response['product'] = $this->view->product; - } - - if (isset($this->view->products)) { - // Initialized products - - // Render HTML-code of products and write to the response buffer - $response['products'] = $this->view->render('catalog/elements/products.html'); - } - - if (isset($this->view->filters)) { - // Initialized filters - - // Render HTML-code of filters and write to the response buffer - $response['filters'] = $this->view->render('catalog/elements/filters.html'); - } - - // Initializing a response headers - header('Content-Type: application/json'); - header('Content-Encoding: none'); - header('X-Accel-Buffering: no'); - - // Initializing of the output buffer - ob_start(); - - // Generating the reponse - echo json_encode( - $response + [ - 'errors' => $this->errors - ] - ); - - // Initializing a response headers - header('Content-Length: ' . ob_get_length()); - - // Sending and deinitializing of the output buffer - ob_end_flush(); - flush(); - - // Exit (success) - return null; + // Deinitializing rendered page + unset($page); } - // Exit (fail) + // Exit (success/fail) return null; } } diff --git a/mirzaev/arming_bot/system/controllers/core.php b/mirzaev/arming_bot/system/controllers/core.php index ad36da0..c9c2456 100755 --- a/mirzaev/arming_bot/system/controllers/core.php +++ b/mirzaev/arming_bot/system/controllers/core.php @@ -16,55 +16,90 @@ use mirzaev\arming_bot\views\templater, mirzaev\arming_bot\models\enumerations\currency; // Framework for PHP -use mirzaev\minimal\controller; +use mirzaev\minimal\core as minimal, + mirzaev\minimal\controller, + mirzaev\minimal\http\response, + mirzaev\minimal\http\enumerations\status; /** * Core of controllers * * @package mirzaev\arming_bot\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 */ class core extends controller { /** - * Postfix for name of controllers files - */ - final public const string POSTFIX = ''; - - /** - * Instance of the settings + * Settings + * + * @var settings $settings Instance of the settings */ protected readonly settings $settings; /** - * Instance of the session + * Session + * + * @var session|null $session Instance of the session */ protected readonly session $session; /** - * Instance of the account + * Account + * + * @var account|null $account Instance of the account */ protected readonly ?account $account; /** - * Instance of the cart + * 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; /** - * Registry of errors + * 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' => [], @@ -72,8 +107,9 @@ class core extends controller ]; /** - * Constructor of an instance + * Constructor * + * @param minimal $core Initialize a controller? * @param bool $initialize Initialize a controller? * * @return void @@ -82,13 +118,13 @@ class core extends controller * 1. settings account и session не имеют проверок на возврат null * 2. TRANSIT EVERYTHING TO MIDDLEWARES */ - public function __construct(bool $initialize = true) + 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; + if (isset($_SERVER['HTTP_USER_AGENT']) && $_SERVER['HTTP_USER_AGENT'] === 'nginx-ssl early hints') return status::bruh->label; // For the extends system - parent::__construct($initialize); + parent::__construct(core: $core); if ($initialize) { // Initializing is requested @@ -107,9 +143,7 @@ class core extends controller // Handle a problems with initializing a session if (!empty($this->errors['session'])) exit(1); - - // телеграм не сохраняет куки - /* else if ($_COOKIE["session"] !== $this->session->hash) { + else if ($_COOKIE["session"] !== $this->session->hash) { // Hash of the session is changed (implies that the session has expired and recreated) // Write a new hash of the session to cookies @@ -124,7 +158,10 @@ class core extends controller 'samesite' => 'strict' ] ); - } */ + } + + // Initializing registry of account errors + $this->errors['account']; // Initializing of the account $this->account = $this->session->account($this->errors['account']); @@ -204,21 +241,4 @@ class core extends controller } } } - - /** - * Check of initialization - * - * Checks whether a property is initialized in a document instance from ArangoDB - * - * @param string $name Name of the property from ArangoDB - * - * @return bool The property is initialized? - */ - public function __isset(string $name): bool - { - // Check of initialization of the property and exit (success) - return match ($name) { - default => isset($this->{$name}) - }; - } } diff --git a/mirzaev/arming_bot/system/controllers/deliveries/cdek.php b/mirzaev/arming_bot/system/controllers/deliveries/cdek.php deleted file mode 100755 index a5b735a..0000000 --- a/mirzaev/arming_bot/system/controllers/deliveries/cdek.php +++ /dev/null @@ -1,78 +0,0 @@ - - */ -final class cdek extends core -{ - /** - * Registry of errors - */ - protected array $errors = [ - 'delivery' => [] - ]; - - /** - * Calculate - * - * @param array $parameters Parameters of the request (POST + GET) - */ - public function calculate(array $parameters = []): ?string - { - if ($_SERVER['REQUEST_METHOD'] === 'POST') { - // POST request - - $this->model::calculate(); - - die; - - // Initializing a response headers - header('Content-Type: application/json'); - header('Content-Encoding: none'); - header('X-Accel-Buffering: no'); - - // Initializing of the output buffer - ob_start(); - - // Generating the reponse - echo json_encode( - [ - 'main' => '', - 'errors' => $this->errors - ] - ); - - // Initializing a response headers - header('Content-Length: ' . ob_get_length()); - - // Sending and deinitializing of the output buffer - ob_end_flush(); - flush(); - - // Exit (success) - return null; - } - - // Exit (fail) - return null; - } -} diff --git a/mirzaev/arming_bot/system/controllers/delivery.php b/mirzaev/arming_bot/system/controllers/delivery.php new file mode 100755 index 0000000..843c523 --- /dev/null +++ b/mirzaev/arming_bot/system/controllers/delivery.php @@ -0,0 +1,566 @@ + + */ +final class delivery 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' => [], + 'buffer' => [], + 'delivery' => [] + ]; + + /** + * Write + * + * Validate and write delivery data to account and session buffers + * + * @param string|null $company Name of delivery company + * @param ?string $location location + * @param ?string $street Address with street and house + * + * @return null + */ + public function write( + /* string|company|null $company = null, */ + string|null $company = null, + /* string|location|null $location = null, */ + ?string $location = null, + ?string $street = null + ): null { + if (str_contains($this->request->headers['accept'], content::json->value)) { + // Request for JSON response + + // Declaring response buffer + $response = [ + 'ready' => false + ]; + + if (!empty($company)) { + // Received company name + + if (mb_strlen($company) < 65) { + // Validated company name + + // Declating variable for normalized value of company namme + $normalized = ''; + + // Normalizing company name + if (preg_match('/[\w]+/u', urldecode($company), $matches)) $normalized = $matches[0] ?? ''; + + // Deinitializing unnecessary variables + unset($matches); + + // Writing to the session buffer + $this->core->request(new request('PATCH', '/session/write', protocol::http_3, parameters: ['delivery_company' => $normalized])); + + // Writing to the account buffer + $this->core->request(new request('PATCH', '/account/write', protocol::http_3, parameters: ['delivery_company' => $normalized])); + + // Writing to the buffer of the response + /* $response['company'] = $normalized; */ + + // Initialization buffer of delivery parameters + $delivery = $this->account?->buffer['delivery'] ?? $this->session?->buffer['delivery'] ?? []; + + if (isset($delivery[$normalized])) { + // Initialized delivery company data + + if (!empty($delivery[$normalized]['location']) && !empty($delivery[$normalized]['street'])) { + // Required parameters initialized: company, location, street + + // Writing readiness status to the buffer of the response + $response['ready'] = true; + } + } + + // Deinitializing unnecessary variables + unset($delivery); + + // Writing status of execution to the buffer of the response + $response['status'] = 'success'; + + // Deinitializing variable for normalized value of the parameter + unset($normalized); + } + } else if (!empty($location)) { + // Received location name + + if (mb_strlen($location) < 257) { + // Validated location name + + // Declating variable for result value of location name + $result = ''; + + // Declating variable for separated values of location name + $separated = ''; + + // Separating location name + if (preg_match_all('/[^\W][\w\s\-]+/u', trim(urldecode($location)), $matches)) $separated = $matches[0]; + + // Deinitializing unnecessary variables + unset($matches); + + if (!empty($separated)) { + // Serapated location name + + // Declaring variable for normalized separated values of location name + $normalized = []; + + // Normalizing location name + foreach ($separated as $value) $normalized[] = mb_ucfirst($value); + + // Deinitializing unnecessary variables + unset($separated, $value); + + // Initialization buffer of delivery parameters + $delivery = $this->account?->buffer['delivery'] ?? $this->session?->buffer['delivery'] ?? []; + + if (isset($delivery['company'])) { + // Initialized delivery company + + // Declaring of universalized locations buffer + $locations = null; + + if ($delivery['company'] === 'cdek') { + // Delivery by CDEK + + // Searching for locations by name (first part with spaces before first comma or any non word symbol) + $cdek = cdek::location($normalized[0], $this->errors['delivery'])?->items; + + foreach ($cdek ?? [] as $location) { + // Iterating over found locations + + // Universalizing and writing to locations buffer + $locations[] = [ + 'identifier' => $location->code, + 'name' => $location->city, + 'structure' => [$location->country, $location->region, $location->sub_region], + 'longitude' => $location->longitude, + 'latitude' => $location->latitude, + 'data' => $location + ]; + } + + // Deinitializing of response data from CDEK + unset($cdek, $location); + } + + if (!empty($locations)) { + // Found locations + + // Declaring buffer of validated locations by input values + $buffer = []; + + // Initialization of 80% of received input values (required minimum to match location characteristics) + $minimum = count($normalized) * 80 / 100; + + foreach ($locations as $location) { + // Iterating over locations + + // Declaring variable with score of matching input values with location characteristics + $score = 0; + + foreach ([$location['name'], ...$location['structure']] as $value) { + // Iterating over location characteristics + + foreach ($normalized as $_value) { + // Iterating over normalized parts of location name + + // Match normalized parts of location name with location characteristics using the Levenshtein algorithm + $match = levenshtein($value, $_value); + + if ($match < 3) { + // The values are approximately the same + + if (++$score > $minimum) { + // More than $minimum matches found + + // Writing to buffer of validated locations by normalized parts of location name + $buffer[] = $location; + + // Exit from iteration of location characteristics + break 2; + } + } + } + + // Deinitializing unnecessary variables + unset($_value); + } + + // Deinitializing unnecessary variables + unset($value); + } + + // Deinitializing unnecessary variables + unset($location); + + // Reinitializating locations from buffer of validated locations by input values + $locations = $buffer; + + if (count($locations) === 1) { + // Identificated location + + // Writing to the session buffer + $this->core->request(new request('PATCH', '/session/write', protocol::http_3, parameters: [$name = 'delivery_' . $delivery['company'] . '_location' => $locations[0]])); + + // Writing to the account buffer + $this->core->request(new request('PATCH', '/account/write', protocol::http_3, parameters: [$name => $locations[0]])); + + // Deinitializing unnecessary variables + unset($name); + + // Writing location data to the buffer of the response + /* $response['location'] = [ + 'identifier' => $locations[0]['identifier'], + 'input' => $result = $locations[0]['name'] . ', ' . implode(', ', array_reverse($locations[0]['structure'])) + ]; */ + + if (!empty($delivery[$delivery['company']]['street'])) { + // Required parameters initialized: company, location, street + + // Writing readiness status to the buffer of the response + $response['ready'] = true; + } + + // Writing status of execution to the buffer of the response + $response['status'] = 'success'; + + // Writing locations into response buffer + $response['locations'] = []; + } else { + // Not identificated location + + // Writing to the session buffer + $this->core->request(new request('PATCH', '/session/write', protocol::http_3, parameters: [$name = 'delivery_' . $delivery['company'] . '_location' => null])); + + // Writing to the account buffer + $this->core->request(new request('PATCH', '/account/write', protocol::http_3, parameters: [$name => null])); + + // Deinitializing unnecessary variables + unset($name); + + // Declaring buffer of data to send + $buffer = []; + + foreach ($locations as $location) { + // Iterating over locations + + // Writing to buffer of data to send + $buffer[] = [ + 'name' => $location['name'], + 'structure' => $location['structure'] + ]; + } + + // Deinitializing unnecessary variables + unset($location); + + // Writing locations into response buffer + $response['locations'] = $buffer; + + // Deinitializing buffer of data to send + unset($buffer); + } + } else { + // Not found locations + + // Writing to the session buffer + $this->core->request(new request('PATCH', '/session/write', protocol::http_3, parameters: [$name = 'delivery_' . $delivery['company'] . '_location' => null])); + + // Writing to the account buffer + $this->core->request(new request('PATCH', '/account/write', protocol::http_3, parameters: [$name => null])); + + // Deinitializing unnecessary variables + unset($name); + } + + // Deinitializing unnecessary variables + unset($locations); + } + + // Deinitializing unnecessary variables + unset($delivery, $normalized); + } + + // Writing to the session buffer + $this->core->request(new request('PATCH', '/session/write', protocol::http_3, parameters: ['delivery_location' => $result])); + + // Writing to the account buffer + $this->core->request(new request('PATCH', '/account/write', protocol::http_3, parameters: ['delivery_location' => $result])); + + // Deinitializing unnecessary variables + unset($result, $normalized); + } + } else if (!empty($street)) { + // Received sreet + + if (mb_strlen($street) < 129) { + // Validated street + + // Declating variable for normalized value of the parameter + $normalized = ''; + + // Normalizing street + if (preg_match('/[\w\d\s]+/u', urldecode($street), $matches)) $normalized = mb_ucfirst($matches[0] ?? ''); + + // Deinitializing unnecessary variables + unset($matches); + + // Initialization buffer of delivery parameters + $delivery = $this->account?->buffer['delivery'] ?? $this->session?->buffer['delivery'] ?? []; + + if (isset($delivery['company'])) { + // Initialized delivery company + + // Writing to the session buffer + $this->core->request(new request('PATCH', '/session/write', protocol::http_3, parameters: [$name = 'delivery_' . $delivery['company'] . '_street' => $normalized])); + + // Writing to the account buffer + $this->core->request(new request('PATCH', '/account/write', protocol::http_3, parameters: [$name => $normalized])); + + // Deinitializing unnecessary variables + unset($name); + + if (!empty($delivery[$delivery['company']]['location'])) { + // Required parameters initialized: company, location, street + + // Writing readiness status to the buffer of the response + $response['ready'] = true; + } + + // Writing status of execution to the buffer of the response + $response['status'] = 'success'; + } + + // Deinitializing unnecessary variables + unset($delivery); + + // Writing to the session buffer + $this->core->request(new request('PATCH', '/session/write', protocol::http_3, parameters: ['delivery_street' => $normalized])); + + // Writing to the account buffer + $this->core->request(new request('PATCH', '/account/write', protocol::http_3, parameters: ['delivery_street' => $normalized])); + + // Writing street to the buffer of the response + /* $response['street'] = $normalized; */ + + // Deinitializing unnecessary variables + unset($normalized); + } + } + + // Sending response + $this->response + ->start() + ->clean() + ->sse() + ->json($response + [ + 'errors' => $this->errors + ]) + ->validate($this->request) + ?->body() + ->end(); + + // Deinitializing response buffer + unset($response); + } + + // Exit (success/fail) + return null; + } + + /** + * Calculate + * + * Calculate delivery by validated data from buffers + * and write to the cart buffer + * + * @return null + */ + public function calculate(): null + { + if (str_contains($this->request->headers['accept'], content::json->value)) { + // Request for JSON response + + // Initialization buffer of delivery parameters + $delivery = $this->account?->buffer['delivery'] ?? $this->session?->buffer['delivery'] ?? []; + + if (isset($delivery['company'])) { + // Initialized delivery company + + // Declaring response buffer + $response = []; + + if (!empty($delivery[$delivery['company']]['location']) && !empty($delivery[$delivery['company']]['street'])) { + // Required parameters initialized: company, location, street + + if ($delivery['company'] === 'cdek') { + // Delivery by CDEK + + // Initializing the cart + $this->cart ??= $this->account?->cart() ?? $this->session?->cart(); + + // Initializing products in the cart + $products = $this->cart?->products(language: $this->language, currency: $this->currency); + + if (!empty($products)) { + // Initialized products + + // 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($products, $product); + + if (!empty($formatted)) { + // Formatted products + + // Calculating delivery by validated data from buffers + $cdek = @cdek::calculate( + from_location: 248, + from_street: 'Екатерининская 116', // @todo issues #13 + to_location: $delivery[$delivery['company']]['location']['identifier'], + to_street: $delivery[$delivery['company']]['street'], + tariff: 368, // ИМ : warehouse-terminal (склад-постамат) + // tariff: 486, // Обычная доставка : warehouse-terminal (склад-постамат) + products: $formatted, + date: new datetime(), // @todo weekdays only? + timezones + errors: $this->errors['delivery'] + ); + + // Deinitializing unnecessary variables + unset($formatted); + + if ($cdek) { + // Calculated delivery + + // Writing to the session buffer + $this->core->request( + new request( + 'PATCH', + '/session/write', + protocol::http_3, + parameters: [ + 'delivery_' . $delivery['company'] . '_cost' => $cdek->total_sum, + 'delivery_' . $delivery['company'] . '_days' => $cdek->calendar_max + ] + ) + ); + + // Writing to the account buffer + $this->core->request( + new request( + 'PATCH', + '/account/write', + protocol::http_3, + parameters: [ + 'delivery_' . $delivery['company'] . '_cost' => $cdek->total_sum, + 'delivery_' . $delivery['company'] . '_days' => $cdek->calendar_max + ] + ) + ); + + // Writing to the cart buffer + $this->cart->buffer_write(['delivery' => [ + 'company' => 'cdek', + 'location' => [ + 'identifier' => $delivery[$delivery['company']]['location']['identifier'], + 'name' => $delivery[$delivery['company']]['location']['name'], + ], + 'street' => $delivery[$delivery['company']]['street'], + 'cost' => $cdek->total_sum, + 'days' => $cdek->calendar_max + ]], $this->errors['buffer']); + + // Writing to response buffer + $response['cost'] = $cdek->total_sum; + $response['days'] = $cdek->calendar_max; + } + } + + // Deinitializing unnecessary variables + unset($formatted); + } + } + } + + // Sending response + $this->response + ->start() + ->clean() + ->sse() + ->json($response + [ + 'errors' => $this->errors + ]) + ->validate($this->request) + ?->body() + ->end(); + + // Deinitializing response buffer + unset($response); + } + + // Deinitializing unnecessary variables + unset($delivery); + } + + // Exit (success/fail) + return null; + } +} diff --git a/mirzaev/arming_bot/system/controllers/index.php b/mirzaev/arming_bot/system/controllers/index.php index 50a65f0..fdd7c64 100755 --- a/mirzaev/arming_bot/system/controllers/index.php +++ b/mirzaev/arming_bot/system/controllers/index.php @@ -6,30 +6,79 @@ namespace mirzaev\arming_bot\controllers; // Files of the project use mirzaev\arming_bot\controllers\core, - mirzaev\arming_bot\models\product; + mirzaev\arming_bot\models\menu; + +// Framework for PHP +use mirzaev\minimal\http\enumerations\content; /** - * Index controller + * Controller of pages * * @package mirzaev\arming_bot\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 */ final class index extends core { + /** + * Errors + * + * @var array $errors Registry of errors + */ + protected array $errors = [ + 'session' => [], + 'account' => [], + 'menu' => [] + ]; /** - * Render the main page + * Public offer * - * @param array $parameters Parameters of the request (POST + GET) + * @return null */ - public function index(array $parameters = []): ?string + public function offer(): null { - // Exit (success) - if ($_SERVER['REQUEST_METHOD'] === 'GET') return $this->view->render('index.html'); + if (isset($menu)) { + // - // Exit (fail) + } else { + // Not received ... menu + + // Search for filters and write to the buffer of global variables of view templater + $this->view->menu = menu::_read( + return: 'MERGE(d, { name: d.name.@language })', + sort: 'd.style.order ASC, d.created DESC, d._key DESC', + amount: 4, + parameters: ['language' => $this->language->name], + errors: $this->errors['menu'] + ); + } + + if (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; } } diff --git a/mirzaev/arming_bot/system/controllers/session.php b/mirzaev/arming_bot/system/controllers/session.php index 6f9fcff..a80e9e0 100755 --- a/mirzaev/arming_bot/system/controllers/session.php +++ b/mirzaev/arming_bot/system/controllers/session.php @@ -6,9 +6,12 @@ namespace mirzaev\arming_bot\controllers; // Files of the project use mirzaev\arming_bot\controllers\core, - mirzaev\arming_bot\models\session as model, mirzaev\arming_bot\models\account; +// Framework for PHP +use mirzaev\minimal\http\enumerations\content, + mirzaev\minimal\http\enumerations\status; + // Framework for ArangoDB use mirzaev\arangodb\document; @@ -17,28 +20,52 @@ use mirzaev\arangodb\document; * * @package mirzaev\arming_bot\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 */ final class session extends core { /** - * Registry of errors + * Errors + * + * @var array $errors Registry of errors */ protected array $errors = [ 'session' => [], - 'account' => [] + 'account' => [], + 'buffer' => [] ]; /** * Connect session to the telegram account * - * @param array $parameters Parameters of the request (POST + GET) + * @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 + * + * @return null */ - public function telegram(array $parameters = []): ?string - { - if ($_SERVER['REQUEST_METHOD'] === 'POST') { - // POST request + 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 + ): 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; @@ -57,11 +84,11 @@ final class session extends core } else { // Not found the account - if (count($parameters) > 1 && isset($parameters['hash'])) { + if (isset($user, $chat_instance, $chat_type, $auth_date, $hash)) { + // Received required parameters - $buffer = $parameters; + $buffer = ['user' => $user, 'chat_instance' => $chat_instance, 'chat_type' => $chat_type, 'auth_date' => $auth_date]; - unset($buffer['hash']); ksort($buffer); $prepared = []; @@ -73,17 +100,17 @@ final class session extends core } } - $key = hash_hmac('sha256', require(SETTINGS . DIRECTORY_SEPARATOR . 'key.php'), 'WebAppData', true); - $hash = bin2hex(hash_hmac('sha256', implode(PHP_EOL, $prepared), $key, true)); + $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, $parameters['hash'])) { + if (hash_equals($hash, $_hash)) { // Data confirmed (according to telegram documentation) - if (time() - $parameters['auth_date'] < 86400) { + if (time() - $auth_date < 86400) { // Authorization date less than 1 day ago // Initializing data of the account - $data = json_decode($parameters['user']); + $data = json_decode($user); // Initializing of the account $account = account::initialize( @@ -96,7 +123,11 @@ final class session extends core ], 'domain' => $data->username, 'language' => $data->language_code, - 'messages' => $data->allows_write_to_pm + 'messages' => $data->allows_write_to_pm, + 'chat' => [ + 'type' => $chat_type, + 'instance' => $chat_instance + ] ], $this->errors['account'] ); @@ -121,53 +152,42 @@ final class session extends core } } - // Initializing a response headers - header('Content-Type: application/json'); - header('Content-Encoding: none'); - header('X-Accel-Buffering: no'); - - // Initializing of the output buffer - ob_start(); - - // Generating the reponse - echo json_encode( - [ + // Sending response + $this->response + ->start() + ->clean() + ->sse() + ->json([ 'connected' => (bool) $connected, 'identifier' => $identifier ?? null, 'domain' => $domain ?? null, 'language' => $language?->name ?? null, 'errors' => $this->errors - ] - ); - - // Initializing a response headers - header('Content-Length: ' . ob_get_length()); - - // Sending and deinitializing of the output buffer - ob_end_flush(); - flush(); - - // Exit (success) - return null; + ]) + ->validate($this->request) + ?->body() + ->end(); } - // Exit (fail) + // Exit (success/fail) return null; } /** + * Write + * * Write to the buffer * - * @param array $parameters Parameters of the request (POST + GET) + * @param mixed ...$parameters Parameters for writing to the buffer * - * @return void + * @return null * * @todo переделать под trait buffer */ - public function write(array $parameters = []): void + public function write(mixed ...$parameters): null { - if (!empty($parameters) && $this->session instanceof model) { - // Received parameters and initialized session + if (!empty($parameters) && isset($this->session)) { + // Received parameters and initialized model with buffer trait // Declaring the buffer of deserialized parameters $deserialized = []; @@ -176,21 +196,27 @@ final class session extends core // Iterate over parameters // Validation of the parameter value - if (mb_strlen($value) > 4096) continue; + if (mb_strlen(serialize($value)) > 4096) continue; // Declaring the buffer of deserialized parameter $parameter = null; // Deserializing name to multidimensional array - foreach (array_reverse(explode('_', $name)) as $key) - $parameter = [$key => $parameter ?? (json_validate($value) ? json_decode($value, true, 10) : $value)]; + 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 session document from ArangoDB - if (!empty($deserialized)) $this->session->write($deserialized, $this->errors['session']); + // 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; } } diff --git a/mirzaev/arming_bot/system/models/cart.php b/mirzaev/arming_bot/system/models/cart.php index 49e9d7b..c126626 100755 --- a/mirzaev/arming_bot/system/models/cart.php +++ b/mirzaev/arming_bot/system/models/cart.php @@ -7,6 +7,7 @@ namespace mirzaev\arming_bot\models; // Files of the project use mirzaev\arming_bot\models\core, mirzaev\arming_bot\models\reservation, + mirzaev\arming_bot\models\traits\buffer, mirzaev\arming_bot\models\traits\document as document_trait, mirzaev\arming_bot\models\interfaces\document as document_interface, mirzaev\arming_bot\models\interfaces\collection as collection_interface, @@ -34,7 +35,9 @@ use exception; */ final class cart extends core implements document_interface, collection_interface { - use document_trait; + use document_trait, buffer { + buffer::write as buffer_write; + } /** * Name of the collection in ArangoDB @@ -334,4 +337,113 @@ final class cart extends core implements document_interface, collection_interfac ]; } } + + /* + * 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 . ' 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 . ' 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; + } + + /* + * Order + * + * + * + * @param array &$errors Registry of errors + * + * @return void + */ + public function order(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(order::COLLECTION, order::TYPE, errors: $errors)) { + // Initialized collections + + } else throw new exception('Failed to initialize ' . product::TYPE . ' collection: ' . product::COLLECTION); + } else throw new exception('Failed to initialize ' . reservation::TYPE . ' collection: ' . reservation::COLLECTION); + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + } } diff --git a/mirzaev/arming_bot/system/models/catalog.php b/mirzaev/arming_bot/system/models/catalog.php index 23df79b..6c50d88 100755 --- a/mirzaev/arming_bot/system/models/catalog.php +++ b/mirzaev/arming_bot/system/models/catalog.php @@ -14,6 +14,9 @@ use mirzaev\arming_bot\models\core, mirzaev\arming_bot\models\enumerations\currency, mirzaev\arming_bot\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; @@ -24,6 +27,9 @@ use avadim\FastExcelReader\Excel as excel; // Built-in libraries use exception; +// GD library +use GdImage as image; + /** * Model of the catalog * @@ -143,10 +149,19 @@ final class catalog extends core // Not initialized the category // Creating the category - $_id = $created = category::write((int) $row['identifier'], [$language->name => $row['name']], $row['position'] ?? null, $errors); + $_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('d._id == @_id', parameters: ['_id' => $_id], errors: $errors); + $category = category::_read( + filter: 'd._id == @_id', + parameters: ['_id' => $_id], + errors: $errors + ); // Incrementing the counter of created categories if ($created) ++$categories_created; @@ -159,7 +174,11 @@ final class catalog extends core // Received the ascendant category // Initializing the ascendant category - $ascendant = category::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $row['category']], errors: $errors); + $ascendant = category::_read( + filter: 'd.identifier == @identifier', + parameters: ['identifier' => (int) $row['category']], + errors: $errors + ); if ($ascendant instanceof category) { // Found the ascendant category @@ -176,13 +195,14 @@ final class catalog extends core // Received images // Initializing new images of the category - $images = explode(' ', trim($row['images'])); + $images = explode(' ', mb_trim($row['images'])); // Reinitialize images? (true, if no images found or their amount does not match) - $reinitialize = !$category->images || count($category->images) !== count($images); + /* $reinitialize = !$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) foreach ($category->images as $key => $image) if ($reinitialize = $image['source'] !== $images[$key]) break; */ if ($reinitialize) { // Requested reinitialization of images @@ -190,35 +210,93 @@ final class catalog extends core // Initializing the buffer of images $buffer = []; - foreach ($images as $index => $image) { + foreach ($images as $index => $file) { // Iterating over new images // Skipping empty URI`s - if (empty($image = trim($image))) continue; + if (empty($file = mb_trim($file))) continue; - // Initializing path to directory of the image in storage - $directory = DIRECTORY_SEPARATOR . 'categories' . DIRECTORY_SEPARATOR . $row['identifier']; + // Initializing path to directory of images in storage + $directory = DIRECTORY_SEPARATOR . 'categories' . DIRECTORY_SEPARATOR . $row['identifier'] . DIRECTORY_SEPARATOR . $index; // Initializing URL of the image in storage $url = STORAGE . $directory; - // Initializing URN of the image in storage - $urn = $index . '.jpg'; - - // Initializing URI of the image in storage - $uri = $url . DIRECTORY_SEPARATOR . $urn; - // Initializing the directory in storage if (!file_exists($url)) mkdir($url, 0775, true); - if (static::yandex($image, $uri, errors: $errors)) { - // The image is downloaded + if ($downloaded = static::yandex( + uri: $file, + destination: $url, + name: 'source', + errors: $errors + )) { + // The image is downloaded and initialized data of the image in storage - // Writing the image to the buffer if images - $buffer[] = [ - 'source' => $image, - 'storage' => $directory . DIRECTORY_SEPARATOR . $urn - ]; + // 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); + } + + // 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." . $downloaded['content']->extension(); + + // Saving the image + imagePng($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' => $file, + 'storage' => [ + 'source' => $directory . DIRECTORY_SEPARATOR . $downloaded['name'] . '.' . $downloaded['content']->extension(), + ] + $resized + ]; + } } } @@ -232,7 +310,7 @@ final class catalog extends core // Incrementing the counter of updated categories if ($updated && !$created) ++$categories_updated; - } else throw new exception("Failed to initialize category: {$row['name']} ($number)"); + } else throw new exception('Failed to initialize category: ' . $row['name'] . " ($number)"); } // Writing to the registry of handled categories and products @@ -313,15 +391,15 @@ final class catalog extends core // Creating the product $_id = product::write( - (int) $row['identifier'], - [$language->name => $row['name']], - [$language->name => $row['description']], - [$currency->name => (float) $row['cost']], - (float) $row['weight'], - ['x' => $row['x'], 'y' => $row['y'], 'z' => $row['z']], - [$language->name => $row['brand']], - [$language->name => $row['compatibility']], - $row['position'] ?? null, + 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: (int) $row['position'] ?? null, errors: $errors ); @@ -339,7 +417,10 @@ final class catalog extends core // Received the category // Initializing the category - $category = category::_read(sprintf('d.identifier == %u', (int) $row['category']), errors: $errors); + $category = category::_read( + filter: sprintf('d.identifier == %u', (int) $row['category']), + errors: $errors + ); if ($category instanceof category) { // Found the ascendant category @@ -356,13 +437,14 @@ final class catalog extends core // Received images // Initializing new images of the category - $images = explode(' ', trim($row['images'])); + $images = explode(' ', mb_trim($row['images'])); // Reinitialize images? (true, if no images found or their amount does not match) - $reinitialize = !$product->images || count($product->images) !== count($images); + /* $reinitialize = !$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) foreach ($product->images as $key => $image) if ($reinitialize = $image['source'] !== $images[$key]) break; */ if ($reinitialize) { // Requested reinitialization of images @@ -370,39 +452,97 @@ final class catalog extends core // Initializing the buffer of images $buffer = []; - foreach ($images as $index => $image) { + foreach ($images as $index => $file) { // Iterating over new images // Skipping empty URI`s - if (empty($image = trim($image))) continue; + if (empty($file = mb_trim($file))) continue; - // Initializing path to directory of the image in storage - $directory = DIRECTORY_SEPARATOR . 'products' . DIRECTORY_SEPARATOR . $row['identifier']; + // Initializing path to directory of images in storage + $directory = DIRECTORY_SEPARATOR . 'products' . DIRECTORY_SEPARATOR . $row['identifier'] . DIRECTORY_SEPARATOR . $index; // Initializing URL of the image in storage $url = STORAGE . $directory; - // Initializing URN of the image in storage - $urn = $index . '.jpg'; - - // Initializing URI of the image in storage - $uri = $url . DIRECTORY_SEPARATOR . $urn; - // Initializing the directory in storage if (!file_exists($url)) mkdir($url, 0775, true); - if (static::yandex($image, $uri, errors: $errors)) { - // The image is downloaded + if ($downloaded = static::yandex( + uri: $file, + destination: $url, + name: 'source', + errors: $errors + )) { + // The image is downloaded and initialized data of the image in storage - // Writing the image to the buffer if images - $buffer[] = [ - 'source' => $image, - 'storage' => $directory . DIRECTORY_SEPARATOR . $urn - ]; + // 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); + } + + // 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." . $downloaded['content']->extension(); + + // Saving the image + imagePng($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' => $file, + 'storage' => [ + 'source' => $directory . DIRECTORY_SEPARATOR . $downloaded['name'] . '.' . $downloaded['content']->extension(), + ] + $resized + ]; + } } } - // Initializing images of the category + // Initializing images of the product $product->images = $buffer; } } @@ -410,9 +550,9 @@ final class catalog extends core // Writing in ArangoDB $updated = document::update($product->__document(), errors: $errors); - // Incrementing the counter of updated categories + // Incrementing the counter of updated products if ($updated && !$created) ++$products_updated; - } else throw new exception("Failed to initialize product: {$row['name']} ($number)"); + } else throw new exception('Failed to initialize product: ' . $row['name'] . " ($number)"); } // Writing to the registry of handled categories and products @@ -446,16 +586,25 @@ final class catalog extends core $category instanceof category && array_search($category->identifier, $handled['categories']) === false ) { - // Not found identifier of the product in the buffer of handled categories and products + // Not found identifier of the category in the buffer of handled categories and products // Deleting images of the category from storage - static::delete(STORAGE . DIRECTORY_SEPARATOR . 'categories' . DIRECTORY_SEPARATOR . $category->identifier, errors: $errors); + static::delete( + directory: STORAGE . DIRECTORY_SEPARATOR . 'categories' . DIRECTORY_SEPARATOR . $category->identifier, + errors: $errors + ); // Deleting entries of the category in ArangoDB - entry::banish($category, errors: $errors); + entry::banish( + document: $category, + errors: $errors + ); // Deleting the category in ArangoDB - document::delete($category->__document(), errors: $errors); + document::delete( + document: $category->__document(), + errors: $errors + ); // Incrementing the counter of deleted categories ++$categories_deleted; @@ -483,13 +632,22 @@ final class catalog extends core // Not found identifier of the product in the buffer of handled categories and products // Deleting images of the product from storage - static::delete(STORAGE . DIRECTORY_SEPARATOR . 'products' . DIRECTORY_SEPARATOR . $product->identifier, errors: $errors); + static::delete( + directory: STORAGE . DIRECTORY_SEPARATOR . 'products' . DIRECTORY_SEPARATOR . $product->identifier, + errors: $errors + ); // Deleting entries of the product in ArangoDB - entry::banish($product, errors: $errors); + entry::banish( + document: $product, + errors: $errors + ); // Deleting the product in ArangoDB - document::delete($product->__document(), errors: $errors); + document::delete( + document: $product->__document(), + errors: $errors + ); // Incrementing the counter of deleted products ++$products_deleted; @@ -497,8 +655,8 @@ final class catalog extends core } // Counting new documents - $categories_new = collection::count(category::COLLECTION, errors: $errors); - $products_new = collection::count(product::COLLECTION, errors: $errors); + $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[] = [ diff --git a/mirzaev/arming_bot/system/models/core.php b/mirzaev/arming_bot/system/models/core.php index 528139c..b11cd94 100755 --- a/mirzaev/arming_bot/system/models/core.php +++ b/mirzaev/arming_bot/system/models/core.php @@ -27,11 +27,6 @@ use exception; */ class core extends model { - /** - * Postfix for name of models files - */ - final public const string POSTFIX = ''; - /** * Path to the file with settings of connecting to the ArangoDB */ @@ -47,7 +42,7 @@ class core extends model /** * Constructor of an instance * - * @param bool $initialize Initialize a model? + * @param bool $initialize Initialize ...? * @param ?arangodb $arangodb Instance of a session of ArangoDB * * @return void diff --git a/mirzaev/arming_bot/system/models/deliveries/cdek.php b/mirzaev/arming_bot/system/models/deliveries/cdek.php index 813b00d..a72ace3 100755 --- a/mirzaev/arming_bot/system/models/deliveries/cdek.php +++ b/mirzaev/arming_bot/system/models/deliveries/cdek.php @@ -15,16 +15,25 @@ use Http\Adapter\Guzzle7\Client as guzzle; // Framework for CDEK use CdekSDK2\Client as client, - CdekSDK2\Dto\CityList as cities; + 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; +use exception, + DateTime as datetime; /** * Model of CDEK * * @package mirzaev\arming_bot\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 */ @@ -33,40 +42,144 @@ final class cdek extends core implements document_interface, collection_interfac use document_trait; /** - * Name of the collection in ArangoDB + * 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 */ - final public const string COLLECTION = 'delivery'; + 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 by CDEK + * 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 + * @return _tariff|null Calculating data, if calculated */ - public static function calculate(array &$errors = []): static|null - { + 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 { - // - /* $client = new client(new guzzle, 'account', 'secure'); */ - $client = new client(new guzzle); - $client->setTest(true); + if (!empty(CDEK)) { + // Initialized CDEK account data - $result = $client->cities()->getFiltered(['country_codes' => 'RU', 'city' => 'зеленогорск']); + // Initializing HTTP-client + $client = new client(new guzzle, CDEK['account'], CDEK['secret']); - if ($result->isOk()) { - // + // Initializing buffer of packages + $packages = []; - //Запрос успешно выполнился - $cities = $client->formatResponseList($result, cities::class); + // Initializing packages from products + foreach ($products as $product) + $packages[] = package::create([ + 'weight' => $product['weight'], + 'width' => $product['x'], + 'height' => $product['y'], + 'length' => $product['z'] + ]); - foreach ($cities->items as $city) { - var_dump($city); + // 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); + } } - die; + // Deinitializing unnecessary variables + unset($client, $packages); } } catch (exception $e) { // Writing to the registry of errors diff --git a/mirzaev/arming_bot/system/models/enumerations/currency.php b/mirzaev/arming_bot/system/models/enumerations/currency.php old mode 100644 new mode 100755 diff --git a/mirzaev/arming_bot/system/models/enumerations/language.php b/mirzaev/arming_bot/system/models/enumerations/language.php old mode 100644 new mode 100755 diff --git a/mirzaev/arming_bot/system/models/enumerations/session.php b/mirzaev/arming_bot/system/models/enumerations/session.php old mode 100644 new mode 100755 diff --git a/mirzaev/arming_bot/system/models/order.php b/mirzaev/arming_bot/system/models/order.php new file mode 100755 index 0000000..95908a1 --- /dev/null +++ b/mirzaev/arming_bot/system/models/order.php @@ -0,0 +1,43 @@ + + */ +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'; +} diff --git a/mirzaev/arming_bot/system/models/session.php b/mirzaev/arming_bot/system/models/session.php index f19b1ff..0564ef8 100755 --- a/mirzaev/arming_bot/system/models/session.php +++ b/mirzaev/arming_bot/system/models/session.php @@ -53,15 +53,15 @@ final class session extends core implements document_interface, collection_inter final public const verification VERIFICATION = verification::hash_else_address; /** - * Constructor of instance + * Constructor * - * Initialize of a session and write them to the $this->document property + * 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 static + * @return void */ public function __construct(?string $hash = null, ?int $expires = null, array &$errors = []) { @@ -71,7 +71,7 @@ final class session extends core implements document_interface, collection_inter if (isset($hash) && $document = $this->hash($hash, errors: $errors)) { // Found the instance of the ArangoDB document of session and received a session hash - + // Writing document instance of the session from ArangoDB to the property of the implementing object $this->__document($document); } else if (static::VERIFICATION === verification::hash_else_address && $document = $this->address($_SERVER['REMOTE_ADDR'], errors: $errors)) { @@ -114,7 +114,7 @@ final class session extends core implements document_interface, collection_inter $session->hash = sodium_bin2hex(sodium_crypto_generichash($_id)); if (document::update($session, errors: $errors)) { - // Update is writed to ArangoDB + // Writed to ArangoDB // Writing instance of the session document from ArangoDB to the property of the implementing object $this->__document($session); @@ -222,7 +222,7 @@ final class session extends core implements document_interface, collection_inter return collection::execute( <<<'AQL' FOR d IN @@collection - FILTER d.hash == @hash && d.expires > $time && d.active == true + FILTER d.hash == @hash && d.expires > @time && d.active == true RETURN d AQL, [ diff --git a/mirzaev/arming_bot/system/models/telegram.php b/mirzaev/arming_bot/system/models/telegram.php index bfa61bd..1c270c2 100755 --- a/mirzaev/arming_bot/system/models/telegram.php +++ b/mirzaev/arming_bot/system/models/telegram.php @@ -9,14 +9,17 @@ use mirzaev\arming_bot\models\core, mirzaev\arming_bot\controllers\core as controller, mirzaev\arming_bot\models\catalog, mirzaev\arming_bot\models\suspension, - mirzaev\arming_bot\models\account; + mirzaev\arming_bot\models\account, + mirzaev\arming_bot\models\enumerations\language, + mirzaev\arming_bot\models\enumerations\currency; // Framework for Telegram use Zanzara\Zanzara, - Zanzara\Context, + Zanzara\Context as context, Zanzara\Telegram\Type\Input\InputFile, Zanzara\Telegram\Type\File\Document as telegram_document, - Zanzara\Middleware\MiddlewareNode as Node; + Zanzara\Middleware\MiddlewareNode as Node, + Zanzara\Telegram\Type\User as user; /** * Model of chat (telegram) @@ -90,11 +93,11 @@ final class telegram extends core * * Команда: /start * - * @param Context $ctx + * @param context $ctx * * @return void */ - public static function menu(Context $ctx): void + public static function menu(context $ctx): void { // Инициализация клавиатуры $keyboard = [ @@ -132,11 +135,11 @@ final class telegram extends core * * Команда: /start * - * @param Context $ctx + * @param context $ctx * * @return void */ - public static function start(Context $ctx): void + public static function start(context $ctx): void { // Главное меню static::menu($ctx); @@ -147,11 +150,11 @@ final class telegram extends core * * Команда: /contacts * - * @param Context $ctx + * @param context $ctx * * @return void */ - public static function contacts(Context $ctx): void + public static function contacts(context $ctx): void { // Отправка сообщения $ctx->sendMessage(static::unmarkdown(<<sendMessage(static::unmarkdown(<<sendMessage( static::unmarkdown(<< [ - 'inline_keyboard' => [ - [ - ['text' => '⚡ Связь с менеджером', 'url' => 'https://git.mirzaev.sexy/mirzaev/mashtrash'], - ['text' => '📨 Почта', 'text' => ''], + Здесь придумать текст для раздела "Компания" + TXT), + [ + 'reply_markup' => [ + 'inline_keyboard' => [ + [ + ['text' => '📄 Публичная оферта', 'web_app' => ['url' => 'https://arming.dev.mirzaev.sexy/offer']], + ] + ] ], - [ - ['text' => '🪖 Сайт', 'url' => ''] - ['text' => '🛒 Wildberries', 'url' => ''] + 'link_preview_options' => [ + 'is_disabled' => true ] ] - ], - 'link_preview_options' => [ - 'is_disabled' => true - ] - ] */ ); } @@ -240,11 +238,11 @@ final class telegram extends core * * Команда: /community * - * @param Context $ctx + * @param context $ctx * * @return void */ - public static function community(Context $ctx): void + public static function community(context $ctx): void { // Отправка сообщения $ctx->sendMessage(static::unmarkdown(<<get('account')?->access['settings']) { // Авторизован доступ к настройкам @@ -308,11 +306,11 @@ final class telegram extends core /** * Запросить файл для импорта товаров (доступ только авторизованным) * - * @param Context $ctx + * @param context $ctx * * @return void */ - public static function import_request(Context $ctx): void + public static function import_request(context $ctx): void { if ($ctx->get('account')?->access['settings']) { // Авторизован доступ к настройкам @@ -337,11 +335,11 @@ final class telegram extends core /** * Импорт товаров (доступ только авторизованным) * - * @param Context $ctx + * @param context $ctx * * @return void */ - public static function import(Context $ctx): void + public static function import(context $ctx): void { if (($account = $ctx->get('account'))?->access['settings']) { // Авторизован доступ к настройкам @@ -410,7 +408,8 @@ final class telegram extends core $products_deleted, $products_old, $products_new, - language: $account->language ?? settings::active()?->language ?? 'en' + language: $account->language ?? settings::active()?->language ?? language::en, // @todo add languages + currency: $account->currency ?? settings::active()?->currency ?? currency::usd // @todo add currencies ); // Отправка сообщения @@ -475,12 +474,12 @@ final class telegram extends core /** * Инициализация аккаунта (middleware) * - * @param Context $ctx + * @param context $ctx * @param Node $next * * @return void */ - public static function account(Context $ctx, Node $next): void + public static function account(context $ctx, Node $next): void { // Выполнение заблокировано? if ($ctx->get('stop')) return; @@ -523,12 +522,12 @@ final class telegram extends core /** * Инициализация статуса технических работ (middleware) * - * @param Context $ctx + * @param context $ctx * @param Node $next * * @return void */ - public static function suspension(Context $ctx, Node $next): void + public static function suspension(context $ctx, Node $next): void { // Выполнение заблокировано? if ($ctx->get('stop')) return; @@ -583,4 +582,122 @@ final class telegram extends core $next($ctx); } } + + /** + * Cart attach + * + * @param context $ctx + * @param string $share Sharing hash + * + * @return void + */ + public static function cart_attach(context $ctx, string $share): void + { + // Initializing account + $account = $ctx->get('account'); + + if ($account) { + // Initialized the account + + // Initializing cart + $cart = cart::_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); + + // Unsharing the cart + $cart->unshare(); + + if ($cart instanceof cart) { + // Initialized the cart + + // Connecting the cart to the account + $edge = $account->connect($cart); + + if (!empty($edge)) { + // Connected the cart to the account + + // Initializing products in the cart + $products = $cart->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; + + // Declaring formatted list of products for message + $list = ''; + + // Initializing iterator of rows + $row = 0; + + foreach ($products as $product) { + // Iterating over products + + // Generating formatted list of products for message + $list .= static::unmarkdown(++$row . '. ' . $product['document']['name'] . ' (' . $product['amount'] . 'шт)') . "\n"; + + // Generating total cost of products + $cost += $product['document']['cost'] * $product['amount']; + } + + // Deinitializing unnecessary variables + unset($products, $product, $row); + + // Initializing currency symbol + $symbol = ($account->currency ?? currency::rub)->symbol(); + + // Initializing delivery cost for message + $delivery_cost = $cart->buffer['delivery']['cost']; + + // Initializing delivery days for message + $delivery_days = $cart->buffer['delivery']['days']; + + // Initializing delivery address for message + $delivery_address = $cart->buffer['delivery']['location']['name'] . ', ' . $cart->buffer['delivery']['street']; + + // Deinitializing unnecessary variables + unset($cart); + + $ctx->sendMessage( + << [ + 'inline_keyboard' => [ + [ + /* ['text' => '🧾 Оплатить', 'web_app' => ['url' => 'https://arming.dev.mirzaev.sexy']] */ + ['text' => '📦 Оформить заказ', 'url' => 'https://auth.robokassa.ru/Merchant/Index.aspx?MerchantLogin=demo&OutSum=11&Description=Покупка в демо магазине&SignatureValue=2c113e992e2c985e43e348ff3c12f32b'], + ] + ], + 'disable_notification' => true + ] + ] + ); + } + + // Deinitializing unnecessary variables + unset($cart, $list); + } + } + + // Deinitializing unnecessary variables + unset($cart); + } + + // Deinitializing unnecessary variables + unset($account); + } } diff --git a/mirzaev/arming_bot/system/models/traits/buffer.php b/mirzaev/arming_bot/system/models/traits/buffer.php index bc91a19..8e1fe0d 100755 --- a/mirzaev/arming_bot/system/models/traits/buffer.php +++ b/mirzaev/arming_bot/system/models/traits/buffer.php @@ -50,20 +50,20 @@ trait buffer // Initialized the collection // Is the instance of the document from ArangoDB are initialized? - if (!isset($this->document)) throw new exception('The instance of the sessoin document from ArangoDB is not 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); + $this->__document()->buffer = array_replace_recursive($this->document->buffer ?? [], $data); // Is the buffer of the instance of the document from ArangoDB exceed 10 megabytes? - if (mb_strlen(json_encode($this->document->buffer)) > 10485760) throw new exception('The buffer size exceeds 10 megabytes'); + if (mb_strlen(json_encode($this->__document()->buffer)) > 10485760) throw new exception('The buffer size exceeds 10 megabytes'); - // Serializing parameters - if ($this->document->language instanceof language) $this->document->language = $this->document->language->name; - if ($this->document->currency instanceof currency) $this->document->currency = $this->document->currency->name; + // Serializing parameters @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); + return document::update($this->__document(), errors: $errors); } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); } catch (exception $e) { // Writing to the registry of errors diff --git a/mirzaev/arming_bot/system/models/traits/yandex/disk.php b/mirzaev/arming_bot/system/models/traits/yandex/disk.php index 40a0c5b..f1b6e64 100755 --- a/mirzaev/arming_bot/system/models/traits/yandex/disk.php +++ b/mirzaev/arming_bot/system/models/traits/yandex/disk.php @@ -4,6 +4,9 @@ declare(strict_types=1); namespace mirzaev\arming_bot\models\traits\yandex; +// Framework for PHP +use mirzaev\minimal\http\enumerations\content; + // Built-in libraries use exception; @@ -22,15 +25,17 @@ trait 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 bool The file is downloaded? + * @return array|false The [destination, name, content] of the downloaded file, if the file was downloaded */ private static function download( string $uri, string $destination, + string|int $name, array &$errors = [] - ): bool { + ): array|false { try { if (!empty($uri)) { // Not empty URI @@ -42,17 +47,44 @@ trait disk $url = "https://cloud-api.yandex.net/v1/disk/public/resources/download?public_key=$uri"; // Checking if the file is available for download - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_exec($ch); - $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); - curl_close($ch); + $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 - // Downloading the file and exit (success) - return file_put_contents($destination, file_get_contents(json_decode(file_get_contents($url))?->href)) > 0; + // 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"); diff --git a/mirzaev/arming_bot/system/public/index.php b/mirzaev/arming_bot/system/public/index.php index 3f1aa64..d095446 100755 --- a/mirzaev/arming_bot/system/public/index.php +++ b/mirzaev/arming_bot/system/public/index.php @@ -4,14 +4,11 @@ declare(strict_types=1); namespace mirzaev\arming_bot; -// Files of the project -use mirzaev\arming_bot\controllers\core as controller, - mirzaev\arming_bot\models\core as model; - // Framework for PHP use mirzaev\minimal\core, - mirzaev\minimal\router; + mirzaev\minimal\route; +// Enabling debugging ini_set('error_reporting', E_ALL); ini_set('display_errors', 1); ini_set('display_startup_errors', 1); @@ -22,59 +19,32 @@ define('VIEWS', realpath('..' . DIRECTORY_SEPARATOR . 'views')); define('STORAGE', realpath('..' . DIRECTORY_SEPARATOR . 'storage')); define('SETTINGS', realpath('..' . DIRECTORY_SEPARATOR . 'settings')); 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')); + // Initialize dependencies -require __DIR__ . DIRECTORY_SEPARATOR - . '..' . DIRECTORY_SEPARATOR - . '..' . DIRECTORY_SEPARATOR - . '..' . DIRECTORY_SEPARATOR - . '..' . DIRECTORY_SEPARATOR - . 'vendor' . DIRECTORY_SEPARATOR - . 'autoload.php'; +require ROOT . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; -// Initialize the router -$router = new router; +// Initializing core +$core = new core(namespace: __NAMESPACE__); -// Initialize routes -$router - ->write('/', 'catalog', 'index', 'GET') - ->write('/', 'catalog', 'index', 'POST') - ->write('/cart', 'cart', 'index', 'GET') - ->write('/cart', 'cart', 'index', 'POST') - ->write('/cart/product', 'cart', 'product', 'POST') - ->write('/cart/summary', 'cart', 'summary', 'POST') - ->write('/account/write', 'account', 'write', 'POST') - ->write('/session/write', 'session', 'write', 'POST') - ->write('/session/connect/telegram', 'session', 'telegram', 'POST') - ->write('/cdek/calculate', 'deliveries\\cdek', 'calculate', 'POST') - /* ->write('/category/$identifier', 'catalog', 'index', 'POST') */ - /* ->write('/category', 'catalog', 'index', 'POST') */ - /* ->write('/product/$identifier', 'catalog', 'product', 'POST') */ -; +// Initializing 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/pay', new route('cart', 'pay', 'cart'), 'POST') + ->write('/order/robokassa', new route('cart', 'robokassa', 'cart'), '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'), 'PATCH') + ->write('/delivery/calculate', new route('delivery', 'calculate'), 'GET'); -/* - -// Initializing of routes -$router - ->write('/', 'catalog', 'index', 'GET') - ->write('/$sex', 'catalog', 'search', 'POST') - ->write('/$search', 'catalog', 'search', 'POST') - ->write('/search', 'catalog', 'search', 'POST') - ->write('/search/$asdasdasd', 'catalog', 'search', 'POST') - ->write('/ebala/$sex/$categories...', 'catalog', 'index', 'POST') - ->write('/$sex/$categories...', 'catalog', 'index', 'POST') - ->write('/$categories...', 'catalog', 'index', 'POST') - ->write('/ebala/$categories...', 'catalog', 'index', 'POST'); - -var_dump($router->routes); -echo "\n\n\n\n\n\n"; -$router - ->sort(); -var_dump($router->routes); */ - -// Initialize the core -$core = new core(namespace: __NAMESPACE__, router: $router, controller: new controller(false), model: new model(false)); - -// Handle the request -echo $core->start(); +// Handling request +$core->start(); diff --git a/mirzaev/arming_bot/system/public/js/authentication.js b/mirzaev/arming_bot/system/public/js/authentication.js deleted file mode 100755 index d44735c..0000000 --- a/mirzaev/arming_bot/system/public/js/authentication.js +++ /dev/null @@ -1,20 +0,0 @@ -"use strict"; - -core.modules.connect(["session", "account", "telegram"]) - .then(() => { - // - const { initData, initDataUnsafe, ...data } = core.telegram.api; - - // - core.session.buffer.write("telegram_program", JSON.stringify(data)); - - if (core.telegram.api.initData.length > 0) { - // - - // - core.account.authentication(); - } - - // - core.telegram.api.ready(); - }); diff --git a/mirzaev/arming_bot/system/public/js/core.js b/mirzaev/arming_bot/system/public/js/core.js index 2c4a6ac..45ad9ba 100755 --- a/mirzaev/arming_bot/system/public/js/core.js +++ b/mirzaev/arming_bot/system/public/js/core.js @@ -52,7 +52,7 @@ class core { static async request( uri = "/", body, - method = "POST", + method = "GET", headers = { "Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json", @@ -320,7 +320,7 @@ Object.assign( // Downloading, importing and writing the module into a core property and into registry of loaded modules core[module] = loaded[module] = - await (await import(`./modules/${module}.js`)).default; + await (await import(`./modules/${module}.mjs`)).default; } // Exit (success) @@ -382,14 +382,14 @@ Object.assign( // Imported the session module // Write to the session buffer - session.default.buffer?.write(name, value); + core.session.buffer?.write(name, value); }); core.modules.connect("account").then(() => { // Imported the account module // Write to the account buffer - account.default.buffer?.write(name, value); + core.account.buffer?.write(name, value); }); // Exit (success) diff --git a/mirzaev/arming_bot/system/public/js/modules/account.js b/mirzaev/arming_bot/system/public/js/modules/account.mjs similarity index 81% rename from mirzaev/arming_bot/system/public/js/modules/account.js rename to mirzaev/arming_bot/system/public/js/modules/account.mjs index dec5d0a..9653b1f 100755 --- a/mirzaev/arming_bot/system/public/js/modules/account.js +++ b/mirzaev/arming_bot/system/public/js/modules/account.mjs @@ -34,7 +34,7 @@ export default class account { const timer_for_response = setTimeout(() => { core.status_loading.setAttribute("disabled", true); - }, 3000); + }, 200); core.modules.connect("telegram").then(() => { // Imported the telegram module @@ -44,6 +44,7 @@ export default class account { .request( "/session/connect/telegram", core.telegram.api.initData, + 'PUT' ) .then((json) => { if (json) { @@ -54,9 +55,12 @@ export default class account { typeof json.errors === "object" && json.errors.length > 0 ) { - // Errors received + // Fail (received errors) + + // Exit (fail) + reject(json); } else { - // Errors not received + // Success (not received errors) if (json.connected === true) { core.status_loading.setAttribute("disabled", true); @@ -97,7 +101,7 @@ export default class account { * @param {string} value Value of the parameter (it can be JSON) * @param {bool} force Ignore the damper? (false) * - * @return {bool} Execution completed with an error? + * @return {bool} Did the execution complete without errors? */ static write = (name, value, force = false) => { core.modules.connect("damper").then( @@ -116,7 +120,7 @@ export default class account { ); // Exit (success) - return false; + return true; }; }; } @@ -128,10 +132,12 @@ core.modules.connect("damper").then(() => { account.buffer.write, { /** - * @name Write + * @name Write (damper) * * @description - * Write to the account buffer (damper) + * 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) @@ -152,10 +158,12 @@ Object.assign( account.buffer.write, { /** - * @name Write + * @name Write (system) * * @description - * Write to the account buffer (system) + * 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) @@ -169,22 +177,38 @@ Object.assign( reject = () => {}, ) { try { - if (typeof name === "string" && typeof value === "string") { + 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}`, - "POST", + "PATCH", ) .then( (json) => { if (json) { // Received a JSON-response - // Exit (success) - resolve(json); + 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(), diff --git a/mirzaev/arming_bot/system/public/js/modules/cart.js b/mirzaev/arming_bot/system/public/js/modules/cart.js deleted file mode 100755 index 30e1d05..0000000 --- a/mirzaev/arming_bot/system/public/js/modules/cart.js +++ /dev/null @@ -1,528 +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 - */ -export default class cart { - /** - * @name Type of the program - */ - static type = "module"; - - /** - * Toggle - * - * Toggle the product in the cart (interface) - * - * @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product - * @param {HTMLElement} product The product element - * @param {bool} remove Remove the product element if json.amount === 0? - * @param {bool} force Ignore the damper? (false) - * - * @return {bool} Execution completed with an error? - */ - static toggle(element, product, remove = false, force = false) { - // Blocking the element - element.setAttribute("disabled", "true"); - - core.modules.connect("damper").then( - () => { - // Imported the damper module - - // Execute under damper - this.product.damper( - element, - product, - "toggle", - undefined, - remove, - force, - ); - }, - () => { - // Not imported the damper module - - // Execute - this.product.system( - element, - product, - "toggle", - undefined, - remove, - force, - ); - }, - ); - - // Exit (success) - return false; - } - - /** - * Write - * - * Write the product in the cart (interface) - * - * @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product - * @param {HTMLElement} product The product element - * @param {number} amount Amount of writings - * @param {bool} remove Remove the product element if json.amount === 0? - * @param {bool} force Ignore the damper? (false) - * - * @return {bool} Execution completed with an error? - */ - static write( - element, - product, - amount = 1, - remove = false, - force = false, - ) { - // Blocking the element - element.setAttribute("disabled", "true"); - - core.modules.connect("damper").then( - () => { - // Imported the damper module - - // Execute under damper - this.product.damper( - element, - product, - "write", - amount, - remove, - force, - ); - }, - () => { - // Not imported the damper module - - // Execute - this.product.system( - element, - product, - "write", - amount, - remove, - force, - ); - }, - ); - - // Exit (success) - return false; - } - - /** - * Delete - * - * Delete the product from the cart (interface) - * - * @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product - * @param {HTMLElement} product The product element - * @param {number} amount Amount of deletings - * @param {bool} remove Remove the product element if json.amount === 0? - * @param {bool} force Ignore the damper? (false) - * - * @return {bool} Execution completed with an error? - */ - static delete( - element, - product, - amount = 1, - remove = false, - force = false, - ) { - // Blocking the element - element.setAttribute("disabled", "true"); - - core.modules.connect("damper").then( - () => { - // Imported the damper module - - // Execute under damper - this.product.damper( - element, - product, - "delete", - amount, - remove, - force, - ); - }, - () => { - // Not imported the damper module - - // Execute - this.product.system( - element, - product, - "delete", - amount, - remove, - force, - ); - }, - ); - - // Exit (success) - return false; - } - - /** - * Set - * - * Set amount of the product in the cart (interface) - * - * @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product - * @param {HTMLElement} product The product element - * @param {number} amount Amount of the product in the cart to be setted - * @param {bool} remove Remove the product element if json.amount === 0? - * @param {bool} force Ignore the damper? (false) - * - * @return {bool} Execution completed with an error? - */ - static set( - element, - product, - amount = 1, - remove = false, - force = false, - ) { - // Blocking the element - element.setAttribute("disabled", "true"); - - core.modules.connect("damper").then( - () => { - // Imported the damper module - - // Execute under damper - this.product.damper( - element, - product, - "set", - amount, - remove, - force, - ); - }, - () => { - // Not imported the damper module - - // Execute - this.product.system( - element, - product, - "set", - amount, - remove, - force, - ); - }, - ); - - // Exit (success) - return false; - } - - /** - * The product - * - * Handle the product in the cart (system) - * - * @param {HTMLButtonElement|HTMLInputElement} element Handler element of the product - * @param {HTMLElement} product The product element - * @param {string} type Type of action with the product - * @param {number} amount Amount of product to handle - * @param {bool} remove Remove the product element if json.amount === 0? - * - * @return {bool} Execution completed with an error? - */ - static product( - element, - product, - type, - amount = null, - remove = false, - ) { - core.modules.connect("damper").then( - () => { - // Imported the damper module - - // Execute under damper - this.product.damper( - element, - product, - type, - amount, - remove, - force, - ); - }, - () => { - // Not imported the damper module - - // Execute - this.product.system( - element, - product, - type, - amount, - remove, - force, - ); - }, - ); - - // Exit (success) - return false; - } - - /** - * @name Summary - * - * @description - * Initialize summary of products the cart (system) - * - * @return {Promise} - */ - static async summary() { - // Request - return await core.request("/cart/summary") - .then((json) => { - if (json) { - // Received a JSON-response - - if ( - json.errors !== null && - typeof json.errors === "object" && - json.errors.length > 0 - ) { - // Fail (received errors) - } else { - // Success (not received errors) - - // Initializing the summary amount element - const amount = document.getElementById("amount"); - - if (amount instanceof HTMLElement) { - // Initialized the summary amount element - - // Writing summmary amount into the summary amount element - amount.innerText = json.amount; - } - - // Initializing the summary cost element - const cost = document.getElementById("cost"); - - if (cost instanceof HTMLElement) { - // Initialized the summary cost element - - // Writing summmary cost into the summary cost element - cost.innerText = json.cost; - } - } - } - }); - } -} - -core.modules.connect("damper").then(() => { - // Imported the damper module - - Object.assign( - cart.product, - { - /** - * Toggle - * - * Toggle the product in the cart (damper) - * - * @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product - * @param {HTMLElement} product The product element - * @param {bool} remove Remove the product element if json.amount === 0? - * @param {bool} force Ignore the damper? (false) - * - * @return {Promise} - */ - damper: core.damper( - (...variables) => cart.product.system(...variables).then(cart.summary), - 300, - 6, - ), - }, - ); -}); - -Object.assign( - cart.product, - { - /** - * The product - * - * Handle the product in the cart (system) - * - * @param {HTMLButtonElement|HTMLInputElement} element Handler element of the product - * @param {HTMLElement} product The product element - * @param {string} type Type of action with the product - * @param {number} amount Amount of product to handle - * @param {bool} remove Remove the product element if json.amount === 0? - * - * @return {Promise|null} - */ - async system( - element, - product, - type, - amount = null, - remove = false, - resolve = () => {}, - reject = () => {}, - ) { - try { - if ( - (element instanceof HTMLButtonElement || - element instanceof HTMLInputElement) && - product instanceof HTMLElement - ) { - // Validated - - // Initializing the buffer of request body - let request = ""; - - // Initializing of identifier of the product - const identifier = +product.getAttribute( - "data-product-identifier", - ); - - if (typeof identifier === "number") { - // Validated identifier - - // Writing to the buffer of request body - request += "&identifier=" + identifier; - - if ( - type === "toggle" || - type === "write" || - type === "delete" || - type === "set" - ) { - // Validated type - - // Writing to the buffer of request body - request += "&type=" + type; - - console.log(type, amount); - if ( - (type === "toggle" && - amount === null || - typeof amount === "undefined") || - (type === "set" && - amount === 0 || - amount === 100) || - typeof amount === "number" && - amount > 0 && - amount < 100 - ) { - // Validated amount - - console.log(amount); - if (type !== "toggle") { - // Not a toggle request - - // Writing to the buffer of request body - request += "&amount=" + amount; - } - - // Request - return await core.request( - "/cart/product", - request, - ) - .then( - (json) => { - if (json) { - // Received a JSON-response - - if ( - json.errors !== null && - typeof json.errors === "object" && - json.errors.length > 0 - ) { - // Fail (received errors) - } else { - // Success (not received errors) - - if (remove && json.amount === 0) { - // Requested deleting of the product element when there is no the product in the cart - - // Deleting the product element - product.remove(); - } else { - // Not requested deleting the product element when there is no the product in the cart - - // Unblocking the element - element.removeAttribute("disabled"); - - // Writing offset of hue-rotate to indicate that the product is in the cart - product.style.setProperty( - "--hue-rotate-offset", - json.amount + "0deg", - ); - - // Writing attribute with amount of the product in the cart - product.setAttribute( - "data-product-amount", - json.amount, - ); - - // Initializing the amount element - const amounts = product.querySelectorAll( - '[data-product-parameter="amount"]', - ); - - for (const amount of amounts) { - // Iterating over an amount elements - - if (amount instanceof HTMLInputElement) { - // The element - - // Writing amount of the product in the cart - amount.value = json.amount; - } else { - // Not the element - - // Writing amount of the product in the cart - amount.innerText = json.amount; - } - } - } - - // Exit (success) - resolve(); - } - } - }, - () => reject(), - ); - } - } - } - } - } catch (e) { - // Exit (fail) - reject(e); - } - }, - }, -); - -// Connecting to the core -if (!core.cart) core.cart = cart; diff --git a/mirzaev/arming_bot/system/public/js/modules/cart.mjs b/mirzaev/arming_bot/system/public/js/modules/cart.mjs new file mode 100755 index 0000000..41940d0 --- /dev/null +++ b/mirzaev/arming_bot/system/public/js/modules/cart.mjs @@ -0,0 +1,902 @@ +"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 + */ +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 in 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
@@ -26,14 +26,14 @@
diff --git a/mirzaev/arming_bot/system/views/themes/default/cart/elements/summary.html b/mirzaev/arming_bot/system/views/themes/default/cart/elements/summary.html index 8dd7c47..3f1a5c0 100755 --- a/mirzaev/arming_bot/system/views/themes/default/cart/elements/summary.html +++ b/mirzaev/arming_bot/system/views/themes/default/cart/elements/summary.html @@ -1,13 +1,19 @@ {% if cart.products is not empty %}
+ class="column unselectable">
{{ cart.summary.amount ?? 0 }} {{ language.name == "ru" ? "товаров на сумму" : "products worth" }} {{ cart.summary.cost ?? 0 }} -
diff --git a/mirzaev/arming_bot/system/views/themes/default/cart/page.html b/mirzaev/arming_bot/system/views/themes/default/cart/page.html index 8ee16e2..6dc994a 100755 --- a/mirzaev/arming_bot/system/views/themes/default/cart/page.html +++ b/mirzaev/arming_bot/system/views/themes/default/cart/page.html @@ -20,7 +20,8 @@ {% block js %} {{ parent() }} - - + + + {% endblock %} diff --git a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/categories.html b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/categories.html index 8587705..ed335a5 100755 --- a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/categories.html +++ b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/categories.html @@ -1,12 +1,13 @@ {% if categories is not empty %}
{% for category in categories %} - {% if category.images %} - {{ category.name }} + {{ category.name }} {% endif %}

{{ category.name }}

diff --git a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/filters.html b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/filters.html index cae919b..dd8d208 100755 --- a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/filters.html +++ b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/filters.html @@ -2,21 +2,22 @@
{% set buffer_brand = session.buffer.catalog.filters.brand ?? account.buffer.catalog.filters.brand %} - - - -
{% endif %} diff --git a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/products.html b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/products.html index e3b23ce..a16fcec 100755 --- a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/products.html +++ b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/products.html @@ -1,23 +1,24 @@ -{% macro card(product) %} -{% set title = product.name ~ ' ' ~ product.brand ~ format_dimensions(product.dimensions.x, +{% macro card(cart, product) %} +{% set title = product.brand ~ format_dimensions(product.dimensions.x, product.dimensions.y, product.dimensions.z, ' ') ~ ' ' ~ product.weight ~ 'г' %} -{% set amount = cart[product.getId()].amount ?? 0 %} - {% endmacro %} @@ -26,14 +27,14 @@ product.dimensions.y, product.dimensions.z, ' ') ~ ' ' ~ product.weight ~ 'г' %
{% for product in products %} {% if loop.index % 2 == 1 %} - {{ _self.card(product) }} + {{ _self.card(cart, product) }} {% endif %} {% endfor %}
{% for product in products %} {% if loop.index % 2 == 0 %} - {{ _self.card(product) }} + {{ _self.card(cart, product) }} {% endif %} {% endfor %}
diff --git a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/search.html b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/search.html index 2f2d8bb..ef25fc4 100755 --- a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/search.html +++ b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/search.html @@ -2,6 +2,6 @@ {% set buffer_search = session.buffer.catalog.search.text ?? account.buffer.catalog.search.text %} diff --git a/mirzaev/arming_bot/system/views/themes/default/catalog/page.html b/mirzaev/arming_bot/system/views/themes/default/catalog/page.html index c91c7bd..1921a4c 100755 --- a/mirzaev/arming_bot/system/views/themes/default/catalog/page.html +++ b/mirzaev/arming_bot/system/views/themes/default/catalog/page.html @@ -21,7 +21,7 @@ {% block js %} {{ parent() }} - - - + + + {% endblock %} diff --git a/mirzaev/arming_bot/system/views/themes/default/connection.html b/mirzaev/arming_bot/system/views/themes/default/connection.html index 53f3816..90fd672 100755 --- a/mirzaev/arming_bot/system/views/themes/default/connection.html +++ b/mirzaev/arming_bot/system/views/themes/default/connection.html @@ -10,5 +10,5 @@ {% endblock %} {% block js %} - + {% endblock %} diff --git a/mirzaev/arming_bot/system/views/themes/default/footer.html b/mirzaev/arming_bot/system/views/themes/default/footer.html index 8322f33..8a06f47 100755 --- a/mirzaev/arming_bot/system/views/themes/default/footer.html +++ b/mirzaev/arming_bot/system/views/themes/default/footer.html @@ -1,10 +1,15 @@ +{% use "/themes/default/offer.html" with css as offer_css, body as offer_body, js as offer_js %} + {% block css %} +{{ block('offer_css') }} {% endblock %} {% block body %}
+{{ block('offer_body') }}
{% endblock %} {% block js %} +{{ block('offer_js') }} {% endblock %} diff --git a/mirzaev/arming_bot/system/views/themes/default/head.html b/mirzaev/arming_bot/system/views/themes/default/head.html index 148a688..05e047b 100755 --- a/mirzaev/arming_bot/system/views/themes/default/head.html +++ b/mirzaev/arming_bot/system/views/themes/default/head.html @@ -12,14 +12,16 @@ {% block css %} - - + + - + {% endblock %} diff --git a/mirzaev/arming_bot/system/views/themes/default/iframes/robokassa.html b/mirzaev/arming_bot/system/views/themes/default/iframes/robokassa.html new file mode 100755 index 0000000..3b37b08 --- /dev/null +++ b/mirzaev/arming_bot/system/views/themes/default/iframes/robokassa.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/mirzaev/arming_bot/system/views/themes/default/index.html b/mirzaev/arming_bot/system/views/themes/default/index.html index f0f2603..3132e44 100755 --- a/mirzaev/arming_bot/system/views/themes/default/index.html +++ b/mirzaev/arming_bot/system/views/themes/default/index.html @@ -15,7 +15,7 @@ {% block body %} - +{{ block('account_body') }} {{ block('header') }}
{% block main %} @@ -27,7 +27,7 @@ {% block js %} {{ parent() }} -{{ block('connection_js') }} + {{ block('account_js') }} {{ block('header_js') }} {{ block('footer_js') }} diff --git a/mirzaev/arming_bot/system/views/themes/default/js.html b/mirzaev/arming_bot/system/views/themes/default/js.html index 8b05cb3..02d8929 100755 --- a/mirzaev/arming_bot/system/views/themes/default/js.html +++ b/mirzaev/arming_bot/system/views/themes/default/js.html @@ -1,11 +1,12 @@ {% block js %} - + - - - + + + + {% if javascript is not empty %} - + + {% endblock %}