BIG UPDATE + resolved #5, resolved #6, resolved #7, resolved #8, resolved #10, resolved #11, resolved #14, resolved #18, resolved #19, resolved #20
This commit is contained in:
parent
cf0e32e954
commit
58f065f312
139
README.md
139
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<br/>
|
||||
<br/>
|
||||
**edgeDefinition:** entry<br/>
|
||||
**fromCollections:** categoy, product<br/>
|
||||
**Name:** catalog<br>
|
||||
<br>
|
||||
* Relatin 1
|
||||
**edgeDefinition:** entry<br>
|
||||
**fromCollections:** categoy, product<br>
|
||||
**toCollections:** category
|
||||
|
||||
* Relation 2
|
||||
**edgeDefinition:** reservation<br>
|
||||
**fromCollections:** product<br>
|
||||
**toCollections:** cart
|
||||
|
||||
2. Create a Graph with the specified values
|
||||
**Name:** sessions<br/>
|
||||
<br/>
|
||||
**edgeDefinition:** connect<br/>
|
||||
**fromCollections:** account<br/>
|
||||
**toCollections:** session
|
||||
**Name:** users<br>
|
||||
<br>
|
||||
* Relation 1
|
||||
**edgeDefinition:** connect<br>
|
||||
**fromCollections:** cart, session<br>
|
||||
**toCollections:** account, session
|
||||
|
||||
**Orphan Collections:** product
|
||||
|
||||
3. Create indexes for the "product" collection
|
||||
**Type:** "Inverted Index"<br/>
|
||||
**Fields:** name.ru<br/>
|
||||
**Analyzer:** "text_ru"<br/>
|
||||
**Search field:** true<br/>
|
||||
**Name:** name_ru<br/>
|
||||
<br/>
|
||||
*Add indexes for all search parameters and for all languages (search language is selected based on the user's language, <br/>
|
||||
otherwise from the default language specified in the active settings from **settings** collection document)*<br/>
|
||||
<br/>
|
||||
*See fields in the `mirzaev/arming_bot/models/product`<br/>
|
||||
**Type:** "Inverted Index"<br>
|
||||
**Fields:** name.ru<br>
|
||||
**Analyzer:** "text_ru"<br>
|
||||
**Search field:** true<br>
|
||||
**Name:** name_ru<br>
|
||||
<br>
|
||||
*Add indexes for all search parameters and for all languages (search language is selected based on the user's language, <br>
|
||||
otherwise from the default language specified in the active settings from **settings** collection document)*<br>
|
||||
<br>
|
||||
*See fields in the `mirzaev/arming_bot/models/product`<br>
|
||||
**name.ru**, **description.ru** and **compatibility.ru***
|
||||
|
||||
4. Create a View with the specified values
|
||||
**type:** search-alias (you can also use "arangosearch")<br/>
|
||||
**name:** **product**s_search<br/>
|
||||
**indexes:**
|
||||
**type:** search-alias (you can also use "arangosearch")<br>
|
||||
**name:** **product**s_search<br>
|
||||
**indexes:**<br>
|
||||
<br>
|
||||
You can copy an example of view file from here: `/examples/arangodb/views/products_search.json`
|
||||
|
||||
```json
|
||||
"indexes": [
|
||||
{
|
||||
|
@ -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 ~ /(?<type>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`<br>
|
||||
`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*<br/>
|
||||
You can copy an example of systemd file from here: `/examples/systemd/huesos.service`<br>
|
||||
<br>
|
||||
**Execute:** `sudo cp huesos.service /etc/systemd/system/huesos.service && sudo chmod +x /etc/systemd/system/huesos.service`<br>
|
||||
<br>
|
||||
*before you execute the command think about **what it does** and whether the **paths** are specified correctly*<br>
|
||||
*the configuration file is very simple and you can remake it for any alternative to SystemD that you like*
|
||||
|
||||
|
||||
## Settings
|
||||
Settings of chat-robot and Web App<br/>
|
||||
<br/>
|
||||
Make sure you have a **settings** collection (can be created automatically) and at least one document with the "status" parameter set to "active"
|
||||
Settings of chat-robot and Web App<br>
|
||||
<br>
|
||||
Make sure you have a **settings** collection (can be created automatically) and at least one document with the "status" parameter set to "active"<br>
|
||||
You can copy a clean settings document without comments from here: `/examples/arangodb/collections/settings.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "active",
|
||||
"project": {
|
||||
"name": "NAME_OF_THE_PROJECT"
|
||||
"name": "PROJECT"
|
||||
},
|
||||
"language": "en",
|
||||
"currency": "usd"
|
||||
"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<br/>
|
||||
<br/>
|
||||
**Value:** en<br/>
|
||||
**⚠️ The value will be converted to an instance of enumeration** `mirzaev\arming_bot\models\enumerations\language`
|
||||
|
||||
### Currency
|
||||
Currency for calculations and render of interface, if account or session currency is not initialized<br/>
|
||||
<br/>
|
||||
**Value:** usd<br/>
|
||||
**⚠️ The value will be converted to an instance of enumeration** `mirzaev\arming_bot\models\enumerations\currency`
|
||||
|
||||
## Suspensions
|
||||
System of suspensions of chat-robot and Web App<br/>
|
||||
<br/>
|
||||
Make sure you have a **suspension** collection (can be created automatically)
|
||||
System of suspensions of chat-robot and Web App<br>
|
||||
<br>
|
||||
Make sure you have a **suspension** collection (can be created automatically)<br>
|
||||
You can copy a clean suspension document without comments from here: `/examples/arangodb/collections/suspension.json`
|
||||
```json
|
||||
{
|
||||
"end": 1726068961,
|
||||
"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": "Разрабатываю каталог, поиск и корзину",
|
||||
|
|
|
@ -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"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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 ~ /(?<type>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;
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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 <arsen@mirzaev.sexy>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 <arsen@mirzaev.sexy>
|
||||
*/
|
||||
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());
|
||||
// Deinitializing summary data of the cart
|
||||
unset($summary);
|
||||
}
|
||||
}
|
||||
|
||||
// Sending and deinitializing of the output buffer
|
||||
ob_end_flush();
|
||||
flush();
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <arsen@mirzaev.sexy>
|
||||
*/
|
||||
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
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
$parameters['brand'] = $brand;
|
||||
}
|
||||
|
||||
// Writing to the session buffer
|
||||
$this->session?->write(
|
||||
[
|
||||
'catalog' => [
|
||||
'filters' => [
|
||||
'brand' => null
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
$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];
|
||||
|
||||
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 session buffer
|
||||
$this->core->request(new request('PATCH', '/session/write', protocol::http_3, parameters: ['catalog_sort' => $sort ?? null]));
|
||||
|
||||
// Writing to the account buffer
|
||||
$this->account?->write(
|
||||
[
|
||||
'catalog' => [
|
||||
'search' => [
|
||||
'text' => null
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
// Writing to the session buffer
|
||||
$this->session?->write(
|
||||
[
|
||||
'catalog' => [
|
||||
'search' => [
|
||||
'text' => null
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
$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');
|
||||
// Deinitializing rendered page
|
||||
unset($page);
|
||||
}
|
||||
|
||||
if (isset($this->view->product)) {
|
||||
// Initialized product
|
||||
|
||||
// Writing data of the product to the response buffer @todo GENERATE THIS ON THE SERVER
|
||||
$response['product'] = $this->view->product;
|
||||
}
|
||||
|
||||
if (isset($this->view->products)) {
|
||||
// Initialized products
|
||||
|
||||
// Render HTML-code of products and write to the response buffer
|
||||
$response['products'] = $this->view->render('catalog/elements/products.html');
|
||||
}
|
||||
|
||||
if (isset($this->view->filters)) {
|
||||
// Initialized filters
|
||||
|
||||
// Render HTML-code of filters and write to the response buffer
|
||||
$response['filters'] = $this->view->render('catalog/elements/filters.html');
|
||||
}
|
||||
|
||||
// Initializing a response headers
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Initializing of the output buffer
|
||||
ob_start();
|
||||
|
||||
// Generating the reponse
|
||||
echo json_encode(
|
||||
$response + [
|
||||
'errors' => $this->errors
|
||||
]
|
||||
);
|
||||
|
||||
// Initializing a response headers
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Sending and deinitializing of the output buffer
|
||||
ob_end_flush();
|
||||
flush();
|
||||
|
||||
// Exit (success)
|
||||
return null;
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
// Exit (success/fail)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <arsen@mirzaev.sexy>
|
||||
*/
|
||||
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})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\arming_bot\controllers;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\arming_bot\controllers\core,
|
||||
mirzaev\arming_bot\models\enumerations\language,
|
||||
mirzaev\arming_bot\models\cart,
|
||||
mirzaev\arming_bot\models\product,
|
||||
mirzaev\arming_bot\models\menu;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\document;
|
||||
|
||||
/**
|
||||
* Controller of cdek
|
||||
*
|
||||
* @package mirzaev\arming_bot\controllers
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class cdek extends core
|
||||
{
|
||||
/**
|
||||
* Registry of errors
|
||||
*/
|
||||
protected array $errors = [
|
||||
'delivery' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* Calculate
|
||||
*
|
||||
* @param array $parameters Parameters of the request (POST + GET)
|
||||
*/
|
||||
public function calculate(array $parameters = []): ?string
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// POST request
|
||||
|
||||
$this->model::calculate();
|
||||
|
||||
die;
|
||||
|
||||
// Initializing a response headers
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Initializing of the output buffer
|
||||
ob_start();
|
||||
|
||||
// Generating the reponse
|
||||
echo json_encode(
|
||||
[
|
||||
'main' => '',
|
||||
'errors' => $this->errors
|
||||
]
|
||||
);
|
||||
|
||||
// Initializing a response headers
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Sending and deinitializing of the output buffer
|
||||
ob_end_flush();
|
||||
flush();
|
||||
|
||||
// Exit (success)
|
||||
return null;
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,566 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\arming_bot\controllers;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\arming_bot\controllers\core,
|
||||
mirzaev\arming_bot\models\cart,
|
||||
mirzaev\arming_bot\models\deliveries\cdek;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\http\enumerations\content,
|
||||
mirzaev\minimal\http\enumerations\protocol,
|
||||
mirzaev\minimal\http\request;
|
||||
|
||||
// Build-in libraries
|
||||
use DateTime as datetime;
|
||||
|
||||
/**
|
||||
* Controller of delivery
|
||||
*
|
||||
* @package mirzaev\arming_bot\controllers
|
||||
*
|
||||
* @param model|null $cart Instance of the cart
|
||||
* @param array $errors Registry of errors
|
||||
*
|
||||
* @method null write(string|null $company, ?string $location, ?string $street) Validate and write delivery data to account and session buffers
|
||||
* @method null calculate() Calculate delivery by validated data from buffers
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class delivery extends core
|
||||
{
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
|
@ -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 <arsen@mirzaev.sexy>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <arsen@mirzaev.sexy>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,37 +210,95 @@ 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
|
||||
|
||||
// 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' => $image,
|
||||
'storage' => $directory . DIRECTORY_SEPARATOR . $urn
|
||||
'source' => $file,
|
||||
'storage' => [
|
||||
'source' => $directory . DIRECTORY_SEPARATOR . $downloaded['name'] . '.' . $downloaded['content']->extension(),
|
||||
] + $resized
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initializing images of the category
|
||||
$category->images = $buffer;
|
||||
|
@ -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
|
||||
|
||||
// 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' => $image,
|
||||
'storage' => $directory . DIRECTORY_SEPARATOR . $urn
|
||||
'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[] = [
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <arsen@mirzaev.sexy>
|
||||
*/
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
die;
|
||||
if ($response->isOk()) {
|
||||
// Received response
|
||||
|
||||
// Exit (success)
|
||||
return $client->formatBaseResponse($response, _tariff::class);
|
||||
}
|
||||
}
|
||||
|
||||
// Deinitializing unnecessary variables
|
||||
unset($client, $packages);
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Writing to the registry of errors
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\arming_bot\models;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\arming_bot\models\core,
|
||||
mirzaev\arming_bot\models\reservation,
|
||||
mirzaev\arming_bot\models\traits\document as document_trait,
|
||||
mirzaev\arming_bot\models\interfaces\document as document_interface,
|
||||
mirzaev\arming_bot\models\interfaces\collection as collection_interface,
|
||||
mirzaev\arming_bot\models\enumerations\language,
|
||||
mirzaev\arming_bot\models\enumerations\currency;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
mirzaev\arangodb\document;
|
||||
|
||||
// Library for ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Model of order
|
||||
*
|
||||
* @uses !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
* @package mirzaev\arming_bot\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class order extends core implements document_interface, collection_interface
|
||||
{
|
||||
use document_trait;
|
||||
|
||||
/**
|
||||
* Name of the collection in ArangoDB
|
||||
*/
|
||||
final public const string COLLECTION = 'order';
|
||||
}
|
|
@ -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 = [])
|
||||
{
|
||||
|
@ -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,
|
||||
[
|
||||
|
|
|
@ -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(<<<TXT
|
||||
|
@ -182,11 +185,11 @@ final class telegram extends core
|
|||
/**
|
||||
* Почта
|
||||
*
|
||||
* @param Context $ctx
|
||||
* @param context $ctx
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function _mail(Context $ctx): void
|
||||
public static function _mail(context $ctx): void
|
||||
{
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage(static::unmarkdown(<<<TXT
|
||||
|
@ -204,34 +207,29 @@ final class telegram extends core
|
|||
*
|
||||
* Команда: /company
|
||||
*
|
||||
* @param Context $ctx
|
||||
* @param context $ctx
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function company(Context $ctx): void
|
||||
public static function company(context $ctx): void
|
||||
{
|
||||
// Отправка сообщения
|
||||
$ctx->sendMessage(
|
||||
static::unmarkdown(<<<TXT
|
||||
Здесь придумать текст для раздела "Компания"
|
||||
TXT),
|
||||
/* [
|
||||
[
|
||||
'reply_markup' => [
|
||||
'inline_keyboard' => [
|
||||
[
|
||||
['text' => '⚡ Связь с менеджером', 'url' => 'https://git.mirzaev.sexy/mirzaev/mashtrash'],
|
||||
['text' => '📨 Почта', 'text' => ''],
|
||||
],
|
||||
[
|
||||
['text' => '🪖 Сайт', 'url' => '']
|
||||
['text' => '🛒 Wildberries', 'url' => '']
|
||||
['text' => '📄 Публичная оферта', 'web_app' => ['url' => 'https://arming.dev.mirzaev.sexy/offer']],
|
||||
]
|
||||
]
|
||||
],
|
||||
'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(<<<TXT
|
||||
|
@ -269,11 +267,11 @@ final class telegram extends core
|
|||
*
|
||||
* Команда: /settings
|
||||
*
|
||||
* @param Context $ctx
|
||||
* @param context $ctx
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function settings(Context $ctx): void
|
||||
public static function settings(context $ctx): void
|
||||
{
|
||||
if ($ctx->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(
|
||||
<<<TXT
|
||||
🛒 *Добавлена корзина*
|
||||
|
||||
$list
|
||||
*Стоимость:* $cost$symbol \+ $delivery_cost$symbol \($delivery_days дней\)
|
||||
*Адрес доставки:* $delivery_address
|
||||
TXT,
|
||||
[
|
||||
'reply_markup' => [
|
||||
'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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
});
|
|
@ -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)
|
||||
|
|
|
@ -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,23 +177,39 @@ 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
|
||||
|
||||
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(),
|
||||
);
|
|
@ -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 <arsen@mirzaev.sexy>
|
||||
*/
|
||||
export default class cart {
|
||||
/**
|
||||
* @name Type of the program
|
||||
*/
|
||||
static type = "module";
|
||||
|
||||
/**
|
||||
* Toggle
|
||||
*
|
||||
* Toggle the product in the cart (interface)
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
|
||||
* @param {HTMLElement} product The product element
|
||||
* @param {bool} remove Remove the product element if json.amount === 0?
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Execution completed with an error?
|
||||
*/
|
||||
static toggle(element, product, remove = false, force = false) {
|
||||
// Blocking the element
|
||||
element.setAttribute("disabled", "true");
|
||||
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.product.damper(
|
||||
element,
|
||||
product,
|
||||
"toggle",
|
||||
undefined,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.product.system(
|
||||
element,
|
||||
product,
|
||||
"toggle",
|
||||
undefined,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write
|
||||
*
|
||||
* Write the product in the cart (interface)
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
|
||||
* @param {HTMLElement} product The product element
|
||||
* @param {number} amount Amount of writings
|
||||
* @param {bool} remove Remove the product element if json.amount === 0?
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Execution completed with an error?
|
||||
*/
|
||||
static write(
|
||||
element,
|
||||
product,
|
||||
amount = 1,
|
||||
remove = false,
|
||||
force = false,
|
||||
) {
|
||||
// Blocking the element
|
||||
element.setAttribute("disabled", "true");
|
||||
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.product.damper(
|
||||
element,
|
||||
product,
|
||||
"write",
|
||||
amount,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.product.system(
|
||||
element,
|
||||
product,
|
||||
"write",
|
||||
amount,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete
|
||||
*
|
||||
* Delete the product from the cart (interface)
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
|
||||
* @param {HTMLElement} product The product element
|
||||
* @param {number} amount Amount of deletings
|
||||
* @param {bool} remove Remove the product element if json.amount === 0?
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Execution completed with an error?
|
||||
*/
|
||||
static delete(
|
||||
element,
|
||||
product,
|
||||
amount = 1,
|
||||
remove = false,
|
||||
force = false,
|
||||
) {
|
||||
// Blocking the element
|
||||
element.setAttribute("disabled", "true");
|
||||
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.product.damper(
|
||||
element,
|
||||
product,
|
||||
"delete",
|
||||
amount,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.product.system(
|
||||
element,
|
||||
product,
|
||||
"delete",
|
||||
amount,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set
|
||||
*
|
||||
* Set amount of the product in the cart (interface)
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
|
||||
* @param {HTMLElement} product The product element
|
||||
* @param {number} amount Amount of the product in the cart to be setted
|
||||
* @param {bool} remove Remove the product element if json.amount === 0?
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Execution completed with an error?
|
||||
*/
|
||||
static set(
|
||||
element,
|
||||
product,
|
||||
amount = 1,
|
||||
remove = false,
|
||||
force = false,
|
||||
) {
|
||||
// Blocking the element
|
||||
element.setAttribute("disabled", "true");
|
||||
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.product.damper(
|
||||
element,
|
||||
product,
|
||||
"set",
|
||||
amount,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.product.system(
|
||||
element,
|
||||
product,
|
||||
"set",
|
||||
amount,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The product
|
||||
*
|
||||
* Handle the product in the cart (system)
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement} element Handler element of the product
|
||||
* @param {HTMLElement} product The product element
|
||||
* @param {string} type Type of action with the product
|
||||
* @param {number} amount Amount of product to handle
|
||||
* @param {bool} remove Remove the product element if json.amount === 0?
|
||||
*
|
||||
* @return {bool} Execution completed with an error?
|
||||
*/
|
||||
static product(
|
||||
element,
|
||||
product,
|
||||
type,
|
||||
amount = null,
|
||||
remove = false,
|
||||
) {
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.product.damper(
|
||||
element,
|
||||
product,
|
||||
type,
|
||||
amount,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.product.system(
|
||||
element,
|
||||
product,
|
||||
type,
|
||||
amount,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Summary
|
||||
*
|
||||
* @description
|
||||
* Initialize summary of products the cart (system)
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async summary() {
|
||||
// Request
|
||||
return await core.request("/cart/summary")
|
||||
.then((json) => {
|
||||
if (json) {
|
||||
// Received a JSON-response
|
||||
|
||||
if (
|
||||
json.errors !== null &&
|
||||
typeof json.errors === "object" &&
|
||||
json.errors.length > 0
|
||||
) {
|
||||
// Fail (received errors)
|
||||
} else {
|
||||
// Success (not received errors)
|
||||
|
||||
// Initializing the summary amount <span> element
|
||||
const amount = document.getElementById("amount");
|
||||
|
||||
if (amount instanceof HTMLElement) {
|
||||
// Initialized the summary amount element
|
||||
|
||||
// Writing summmary amount into the summary amount element
|
||||
amount.innerText = json.amount;
|
||||
}
|
||||
|
||||
// Initializing the summary cost <span> element
|
||||
const cost = document.getElementById("cost");
|
||||
|
||||
if (cost instanceof HTMLElement) {
|
||||
// Initialized the summary cost element
|
||||
|
||||
// Writing summmary cost into the summary cost element
|
||||
cost.innerText = json.cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
core.modules.connect("damper").then(() => {
|
||||
// Imported the damper module
|
||||
|
||||
Object.assign(
|
||||
cart.product,
|
||||
{
|
||||
/**
|
||||
* Toggle
|
||||
*
|
||||
* Toggle the product in the cart (damper)
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
|
||||
* @param {HTMLElement} product The product element
|
||||
* @param {bool} remove Remove the product element if json.amount === 0?
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
damper: core.damper(
|
||||
(...variables) => cart.product.system(...variables).then(cart.summary),
|
||||
300,
|
||||
6,
|
||||
),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Object.assign(
|
||||
cart.product,
|
||||
{
|
||||
/**
|
||||
* The product
|
||||
*
|
||||
* Handle the product in the cart (system)
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement} element Handler element of the product
|
||||
* @param {HTMLElement} product The product element
|
||||
* @param {string} type Type of action with the product
|
||||
* @param {number} amount Amount of product to handle
|
||||
* @param {bool} remove Remove the product element if json.amount === 0?
|
||||
*
|
||||
* @return {Promise|null}
|
||||
*/
|
||||
async system(
|
||||
element,
|
||||
product,
|
||||
type,
|
||||
amount = null,
|
||||
remove = false,
|
||||
resolve = () => {},
|
||||
reject = () => {},
|
||||
) {
|
||||
try {
|
||||
if (
|
||||
(element instanceof HTMLButtonElement ||
|
||||
element instanceof HTMLInputElement) &&
|
||||
product instanceof HTMLElement
|
||||
) {
|
||||
// Validated
|
||||
|
||||
// Initializing the buffer of request body
|
||||
let request = "";
|
||||
|
||||
// Initializing of identifier of the product
|
||||
const identifier = +product.getAttribute(
|
||||
"data-product-identifier",
|
||||
);
|
||||
|
||||
if (typeof identifier === "number") {
|
||||
// Validated identifier
|
||||
|
||||
// Writing to the buffer of request body
|
||||
request += "&identifier=" + identifier;
|
||||
|
||||
if (
|
||||
type === "toggle" ||
|
||||
type === "write" ||
|
||||
type === "delete" ||
|
||||
type === "set"
|
||||
) {
|
||||
// Validated type
|
||||
|
||||
// Writing to the buffer of request body
|
||||
request += "&type=" + type;
|
||||
|
||||
console.log(type, amount);
|
||||
if (
|
||||
(type === "toggle" &&
|
||||
amount === null ||
|
||||
typeof amount === "undefined") ||
|
||||
(type === "set" &&
|
||||
amount === 0 ||
|
||||
amount === 100) ||
|
||||
typeof amount === "number" &&
|
||||
amount > 0 &&
|
||||
amount < 100
|
||||
) {
|
||||
// Validated amount
|
||||
|
||||
console.log(amount);
|
||||
if (type !== "toggle") {
|
||||
// Not a toggle request
|
||||
|
||||
// Writing to the buffer of request body
|
||||
request += "&amount=" + amount;
|
||||
}
|
||||
|
||||
// Request
|
||||
return await core.request(
|
||||
"/cart/product",
|
||||
request,
|
||||
)
|
||||
.then(
|
||||
(json) => {
|
||||
if (json) {
|
||||
// Received a JSON-response
|
||||
|
||||
if (
|
||||
json.errors !== null &&
|
||||
typeof json.errors === "object" &&
|
||||
json.errors.length > 0
|
||||
) {
|
||||
// Fail (received errors)
|
||||
} else {
|
||||
// Success (not received errors)
|
||||
|
||||
if (remove && json.amount === 0) {
|
||||
// Requested deleting of the product element when there is no the product in the cart
|
||||
|
||||
// Deleting the product element
|
||||
product.remove();
|
||||
} else {
|
||||
// Not requested deleting the product element when there is no the product in the cart
|
||||
|
||||
// Unblocking the element
|
||||
element.removeAttribute("disabled");
|
||||
|
||||
// Writing offset of hue-rotate to indicate that the product is in the cart
|
||||
product.style.setProperty(
|
||||
"--hue-rotate-offset",
|
||||
json.amount + "0deg",
|
||||
);
|
||||
|
||||
// Writing attribute with amount of the product in the cart
|
||||
product.setAttribute(
|
||||
"data-product-amount",
|
||||
json.amount,
|
||||
);
|
||||
|
||||
// Initializing the amount <span> element
|
||||
const amounts = product.querySelectorAll(
|
||||
'[data-product-parameter="amount"]',
|
||||
);
|
||||
|
||||
for (const amount of amounts) {
|
||||
// Iterating over an amount elements
|
||||
|
||||
if (amount instanceof HTMLInputElement) {
|
||||
// The <input> element
|
||||
|
||||
// Writing amount of the product in the cart
|
||||
amount.value = json.amount;
|
||||
} else {
|
||||
// Not the <input> element
|
||||
|
||||
// Writing amount of the product in the cart
|
||||
amount.innerText = json.amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
},
|
||||
() => reject(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Exit (fail)
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Connecting to the core
|
||||
if (!core.cart) core.cart = cart;
|
|
@ -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 <arsen@mirzaev.sexy>
|
||||
*/
|
||||
export default class cart {
|
||||
/**
|
||||
* @name Type of the program
|
||||
*/
|
||||
static type = "module";
|
||||
|
||||
/**
|
||||
* @name Toggle (interface)
|
||||
*
|
||||
* @description
|
||||
* Toggle the product in the cart
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement|null} element Handler elememnt of the product
|
||||
* @param {HTMLElement} product The product element
|
||||
* @param {bool} remove Remove the product element if json.amount === 0?
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Did the execution complete without errors?
|
||||
*/
|
||||
static toggle(element, product, remove = false, force = false) {
|
||||
// Blocking the element
|
||||
element?.setAttribute("disabled", "true");
|
||||
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.product.damper(
|
||||
element,
|
||||
product,
|
||||
"toggle",
|
||||
undefined,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.product.system(
|
||||
element,
|
||||
product,
|
||||
"toggle",
|
||||
undefined,
|
||||
remove,
|
||||
force,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Write (interface)
|
||||
*
|
||||
* @description
|
||||
* Write the product 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 <button>
|
||||
*
|
||||
* @return {bool} Did the execution complete without errors?
|
||||
*/
|
||||
static summary(button) {
|
||||
// Disabling button
|
||||
button?.setAttribute("disabled", true);
|
||||
|
||||
// Initializing timer of enabling the delivery company <input> (radio) element
|
||||
const enabling = setTimeout(() => {
|
||||
// Enabling button
|
||||
button?.removeAttribute("disabled");
|
||||
}, 3000);
|
||||
|
||||
/**
|
||||
* @name Resolved
|
||||
*
|
||||
* @description
|
||||
* Render the result of the resolved request
|
||||
*
|
||||
* @param {object} json
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
const resolved = (json) => {
|
||||
if (json) {
|
||||
// Received JSON-response
|
||||
|
||||
// Deinitializing timer of enabling the delivery company <input> (radio) element
|
||||
clearTimeout(enabling);
|
||||
|
||||
// Enabling button
|
||||
button?.removeAttribute("disabled");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @name Rejected
|
||||
*
|
||||
* @description
|
||||
* Render the result of the rejected request
|
||||
*
|
||||
* @param {object} json
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
const rejected = (json) => {
|
||||
if (json) {
|
||||
// Received JSON-response
|
||||
|
||||
// Deinitializing timer of enabling the delivery company <input> (radio) element
|
||||
clearTimeout(enabling);
|
||||
|
||||
// Enabling button
|
||||
button?.removeAttribute("disabled");
|
||||
}
|
||||
};
|
||||
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.summary.damper()
|
||||
.then(resolved, rejected);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.summary.system()
|
||||
.then(resolved, rejected);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Share (interface)
|
||||
*
|
||||
* @description
|
||||
* Generate sharing hash of the cart and sent ot to the chat-robot
|
||||
*
|
||||
* @param {HTMLButtonElement} button Button <button>
|
||||
*
|
||||
* @return {bool} Did the execution complete without errors?
|
||||
*/
|
||||
static async share(button) {
|
||||
return await core.modules.connect("telegram").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Disabling button
|
||||
button?.setAttribute("disabled", true);
|
||||
|
||||
// Initializing timer of enabling the delivery company <input> (radio) element
|
||||
const enabling = setTimeout(() => {
|
||||
// Enabling button
|
||||
button?.removeAttribute("disabled");
|
||||
}, 3000);
|
||||
|
||||
/**
|
||||
* @name Resolved
|
||||
*
|
||||
* @description
|
||||
* Render the result of the resolved request
|
||||
*
|
||||
* @param {object} json
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
const resolved = (json) => {
|
||||
if (json) {
|
||||
// Received JSON-response
|
||||
|
||||
// Deinitializing timer of enabling the delivery company <input> (radio) element
|
||||
clearTimeout(enabling);
|
||||
|
||||
// Enabling button
|
||||
button?.removeAttribute("disabled");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @name Rejected
|
||||
*
|
||||
* @description
|
||||
* Render the result of the rejected request
|
||||
*
|
||||
* @param {object} json
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
const rejected = (json) => {
|
||||
if (json) {
|
||||
// Received JSON-response
|
||||
|
||||
// Deinitializing timer of enabling the delivery company <input> (radio) element
|
||||
clearTimeout(enabling);
|
||||
|
||||
// Enabling button
|
||||
button?.removeAttribute("disabled");
|
||||
}
|
||||
};
|
||||
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.share.damper()
|
||||
.then(resolved, rejected);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.share.system()
|
||||
.then(resolved, rejected);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
},
|
||||
// Exit (fail)
|
||||
() => false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
core.modules.connect("damper").then(() => {
|
||||
// Imported the damper module
|
||||
|
||||
Object.assign(
|
||||
cart.product,
|
||||
{
|
||||
/**
|
||||
* @name Product (damper)
|
||||
*
|
||||
* @description
|
||||
* Handle the product in the cart
|
||||
*
|
||||
* @memberof cart.product
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
|
||||
* @param {HTMLElement} product The product element
|
||||
* @param {bool} remove Remove the product element if json.amount === 0?
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
damper: core.damper(
|
||||
(...variables) =>
|
||||
cart.product.system(...variables).then(cart.summary.system),
|
||||
300,
|
||||
6,
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
Object.assign(
|
||||
cart.summary,
|
||||
{
|
||||
/**
|
||||
* @name Summary (damper)
|
||||
*
|
||||
* @description
|
||||
* Initialize summary of products the cart
|
||||
*
|
||||
* @memberof cart.summary
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
damper: core.damper(
|
||||
(...variables) => cart.summary.system(...variables),
|
||||
300,
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
Object.assign(
|
||||
cart.share,
|
||||
{
|
||||
/**
|
||||
* @name Share (damper)
|
||||
*
|
||||
* @description
|
||||
* Generate sharing hash of the cart and sent ot to the chat-robot
|
||||
*
|
||||
* @memberof cart.share
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
damper: core.damper(
|
||||
(...variables) => cart.share.system(...variables),
|
||||
400,
|
||||
),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Object.assign(
|
||||
cart.product,
|
||||
{
|
||||
/**
|
||||
* @name Product (system)
|
||||
*
|
||||
* @description
|
||||
* Handle the product in the cart
|
||||
*
|
||||
* @memberof cart.product
|
||||
*
|
||||
* @param {HTMLButtonElement|HTMLInputElement|null} element Handler element of the product
|
||||
* @param {HTMLElement} product The product element
|
||||
* @param {string} type Type of action with the product
|
||||
* @param {number} amount Amount of product to handle
|
||||
* @param {bool} remove Remove the product element if json.amount === 0?
|
||||
*
|
||||
* @return {Promise|null}
|
||||
*/
|
||||
async system(
|
||||
element,
|
||||
product,
|
||||
type,
|
||||
amount = null,
|
||||
remove = false,
|
||||
resolve = () => {},
|
||||
reject = () => {},
|
||||
) {
|
||||
try {
|
||||
if (product instanceof HTMLElement) {
|
||||
// Validated required arguments
|
||||
|
||||
// Initializing the buffer of request body
|
||||
let request = "";
|
||||
|
||||
// Initializing of identifier of the product
|
||||
const identifier = +product.getAttribute(
|
||||
"data-product-identifier",
|
||||
);
|
||||
|
||||
if (typeof identifier === "number") {
|
||||
// Validated identifier
|
||||
|
||||
// Writing to the buffer of request body
|
||||
request += "&identifier=" + identifier;
|
||||
|
||||
if (
|
||||
type === "toggle" ||
|
||||
type === "write" ||
|
||||
type === "delete" ||
|
||||
type === "set"
|
||||
) {
|
||||
// Validated type
|
||||
|
||||
// Writing to the buffer of request body
|
||||
request += "&type=" + type;
|
||||
|
||||
if (
|
||||
(type === "toggle" &&
|
||||
amount === null ||
|
||||
typeof amount === "undefined") ||
|
||||
(type === "set" &&
|
||||
amount === 0 ||
|
||||
amount === 100) ||
|
||||
typeof amount === "number" &&
|
||||
amount > 0 &&
|
||||
amount < 100
|
||||
) {
|
||||
// Validated amount
|
||||
|
||||
if (type !== "toggle") {
|
||||
// Not a toggle request
|
||||
|
||||
// Writing to the buffer of request body
|
||||
request += "&amount=" + amount;
|
||||
}
|
||||
|
||||
// Request
|
||||
return await core.request(
|
||||
"/cart/product",
|
||||
request,
|
||||
"PATCH",
|
||||
)
|
||||
.then(
|
||||
(json) => {
|
||||
if (json) {
|
||||
// Received a JSON-response
|
||||
|
||||
if (
|
||||
json.errors !== null &&
|
||||
typeof json.errors === "object" &&
|
||||
json.errors.length > 0
|
||||
) {
|
||||
// Fail (received errors)
|
||||
|
||||
core.modules.connect("telegram").then(() => {
|
||||
// Imported the telegram module
|
||||
|
||||
core.telegram.api.HapticFeedback
|
||||
.notificationOccurred("error");
|
||||
});
|
||||
|
||||
// Exit (fail)
|
||||
reject(json);
|
||||
} else {
|
||||
// Success (not received errors)
|
||||
|
||||
core.modules.connect("telegram").then(() => {
|
||||
// Imported the telegram module
|
||||
|
||||
core.telegram.api.HapticFeedback
|
||||
.notificationOccurred("success");
|
||||
});
|
||||
|
||||
core.modules.connect("delivery").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Calculating delivery
|
||||
core.delivery.calculate();
|
||||
},
|
||||
);
|
||||
|
||||
if (remove && json.amount === 0) {
|
||||
// Requested deleting of the product element when there is no the product in the cart
|
||||
|
||||
// Deleting the product element
|
||||
product.remove();
|
||||
} else {
|
||||
// Not requested deleting the product element when there is no the product in the cart
|
||||
|
||||
// Unblocking the element
|
||||
element?.removeAttribute("disabled");
|
||||
|
||||
// Writing offset of hue-rotate to indicate that the product is in the cart
|
||||
product.style.setProperty(
|
||||
"--hue-rotate-offset",
|
||||
json.amount + "0deg",
|
||||
);
|
||||
|
||||
// Writing attribute with amount of the product in the cart
|
||||
product.setAttribute(
|
||||
"data-product-amount",
|
||||
json.amount,
|
||||
);
|
||||
|
||||
// Initializing the amount <span> element
|
||||
const amounts = product.querySelectorAll(
|
||||
'[data-product-parameter="amount"]',
|
||||
);
|
||||
|
||||
for (const amount of amounts) {
|
||||
// Iterating over an amount elements
|
||||
|
||||
if (amount instanceof HTMLInputElement) {
|
||||
// The <input> element
|
||||
|
||||
// Writing amount of the product in the cart
|
||||
amount.value = json.amount;
|
||||
} else {
|
||||
// Not the <input> element
|
||||
|
||||
// Writing amount of the product in the cart
|
||||
amount.innerText = json.amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
},
|
||||
() => reject(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Exit (fail)
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
Object.assign(
|
||||
cart.summary,
|
||||
{
|
||||
/**
|
||||
* @name Summary (system)
|
||||
*
|
||||
* @description
|
||||
* Initialize summary of products the cart
|
||||
*
|
||||
* @memberof cart.summary
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
async system(
|
||||
resolve = () => {},
|
||||
reject = () => {},
|
||||
) {
|
||||
try {
|
||||
// Request
|
||||
return await core.request("/cart/summary", undefined, "GET")
|
||||
.then((json) => {
|
||||
if (json) {
|
||||
// Received a JSON-response
|
||||
|
||||
if (
|
||||
json.errors !== null &&
|
||||
typeof json.errors === "object" &&
|
||||
json.errors.length > 0
|
||||
) {
|
||||
// Fail (received errors)
|
||||
|
||||
core.modules.connect("telegram").then(() => {
|
||||
// Imported the telegram module
|
||||
|
||||
core.telegram.api.HapticFeedback.notificationOccurred(
|
||||
"error",
|
||||
);
|
||||
});
|
||||
|
||||
// Exit (fail)
|
||||
reject(json);
|
||||
} else {
|
||||
// Success (not received errors)
|
||||
|
||||
// Initializing the summary amount <span> element
|
||||
const amount = document.getElementById("amount");
|
||||
|
||||
if (amount instanceof HTMLElement) {
|
||||
// Initialized the summary amount element
|
||||
|
||||
// Writing summary amount into the summary amount element
|
||||
amount.innerText = json.amount;
|
||||
}
|
||||
|
||||
// Initializing the summary cost <span> element
|
||||
const cost = document.getElementById("cost");
|
||||
|
||||
if (cost instanceof HTMLElement) {
|
||||
// Initialized the summary cost element
|
||||
|
||||
core.modules.connect("telegram").then(() => {
|
||||
// Imported the telegram module
|
||||
|
||||
core.telegram.api.HapticFeedback.notificationOccurred(
|
||||
"success",
|
||||
);
|
||||
});
|
||||
|
||||
// Writing summary cost into the summary cost element
|
||||
cost.innerText = json.cost;
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
resolve(json);
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
reject(json);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
// Exit (fail)
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
Object.assign(
|
||||
cart.share,
|
||||
{
|
||||
/**
|
||||
* @name Share (system)
|
||||
*
|
||||
* @description
|
||||
* Generate sharing hash of the cart and sent ot to the chat-robot
|
||||
*
|
||||
* @memberof cart.share
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
async system(
|
||||
resolve = () => {},
|
||||
reject = () => {},
|
||||
) {
|
||||
try {
|
||||
// Request
|
||||
return await core.request("/cart/pay", undefined, "POST")
|
||||
.then((json) => {
|
||||
if (json) {
|
||||
// Received a JSON-response
|
||||
|
||||
if (
|
||||
json.errors !== null &&
|
||||
typeof json.errors === "object" &&
|
||||
json.errors.length > 0
|
||||
) {
|
||||
// Fail (received errors)
|
||||
|
||||
// Exit (fail)
|
||||
reject(json);
|
||||
} else {
|
||||
// Success (not received errors)
|
||||
|
||||
/* if (json.share) {
|
||||
// Received sharing hash
|
||||
|
||||
// Request to the chat-robot
|
||||
core.telegram.api.sendData(
|
||||
JSON.stringify({
|
||||
type: "cart_share",
|
||||
hash: json.share,
|
||||
}),
|
||||
);
|
||||
} */
|
||||
|
||||
if (json.robokassa) {
|
||||
// Received data for the Robokassa acquiring
|
||||
|
||||
// Initializing iframe
|
||||
Robokassa.StartPayment({
|
||||
MerchantLogin: json.robokassa.identifier,
|
||||
OutSum: json.robokassa.cost,
|
||||
InvId: json.robokassa.cart,
|
||||
Description: json.robokassa.description,
|
||||
Culture: json.robokassa.language,
|
||||
Encoding: "utf-8",
|
||||
Settings: JSON.stringify({
|
||||
PaymentMethods: ["BankCard", "SBP"],
|
||||
Mode: "modal",
|
||||
}),
|
||||
SignatureValue: json.robokassa.hash,
|
||||
});
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
resolve(json);
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
reject(json);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
// Exit (fail)
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Connecting to the core
|
||||
if (!core.cart) core.cart = cart;
|
|
@ -42,14 +42,15 @@ export default class catalog {
|
|||
]);
|
||||
|
||||
/**
|
||||
* Search (interface)
|
||||
* @name Search (interface)
|
||||
*
|
||||
* Request search in the catalog and render in the core.main
|
||||
* @description
|
||||
* Request search in the catalog and render result in the core.main
|
||||
*
|
||||
* @param {Event} event Event (keyup)
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} True if an error occurs to continue the event execution
|
||||
* @return {bool} Did the execution complete without errors?
|
||||
*/
|
||||
static search(event, force = false) {
|
||||
// Initializing the search <input> element
|
||||
|
@ -68,7 +69,7 @@ export default class catalog {
|
|||
// Only 1-2 character entered (this is not a search reset and is not an adequate search)
|
||||
|
||||
// Exit (fail)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +97,7 @@ export default class catalog {
|
|||
// Less than 3 character entered (this is not a search reset and is not an adequate search)
|
||||
|
||||
// Exit (fail)
|
||||
return true;
|
||||
return false;
|
||||
} else {
|
||||
// Executed by any else button
|
||||
|
||||
|
@ -136,22 +137,23 @@ export default class catalog {
|
|||
}
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Product card (interface)
|
||||
* @name Product card (interface)
|
||||
*
|
||||
* Request product data and render in core.window
|
||||
* @description
|
||||
* Request product data and render result in core.window
|
||||
*
|
||||
* @param {HTMLElement} button Button of the product <a>
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} True if an error occurs to continue the event execution
|
||||
* @return {bool} Did the execution complete without errors?
|
||||
*/
|
||||
static product(button, force = false) {
|
||||
// Initializing identifier of the category
|
||||
|
@ -175,7 +177,7 @@ export default class catalog {
|
|||
);
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,9 +188,12 @@ core.modules.connect("damper").then(() => {
|
|||
catalog.search,
|
||||
{
|
||||
/**
|
||||
* Search (damper)
|
||||
* @name Search (system)
|
||||
*
|
||||
* Request search in the catalog and render in the core.main
|
||||
* @description
|
||||
* Request search in the catalog and render result in the core.main
|
||||
*
|
||||
* @memberof catalog.search
|
||||
*
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
|
@ -196,7 +201,7 @@ core.modules.connect("damper").then(() => {
|
|||
*/
|
||||
damper: core.damper(
|
||||
(...variables) => catalog.search.system(...variables),
|
||||
1400,
|
||||
300,
|
||||
1,
|
||||
),
|
||||
},
|
||||
|
@ -206,9 +211,12 @@ core.modules.connect("damper").then(() => {
|
|||
catalog.product,
|
||||
{
|
||||
/**
|
||||
* Product card (damper)
|
||||
* @name Product card (damper)
|
||||
*
|
||||
* Request product data and render in core.window
|
||||
* @description
|
||||
* Request product data and render result in the core.window
|
||||
*
|
||||
* @memberof catalog.product
|
||||
*
|
||||
* @param {string} identifier Identifier of the product
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
|
@ -217,7 +225,7 @@ core.modules.connect("damper").then(() => {
|
|||
*/
|
||||
damper: core.damper(
|
||||
(...variables) => catalog.product.system(...variables),
|
||||
400,
|
||||
200,
|
||||
2,
|
||||
),
|
||||
},
|
||||
|
@ -228,9 +236,12 @@ Object.assign(
|
|||
catalog.product,
|
||||
{
|
||||
/**
|
||||
* Product card (system)
|
||||
* @name Product card (system)
|
||||
*
|
||||
* Request product data and render in core.window
|
||||
* @description
|
||||
* Request product data and render result in the core.window
|
||||
*
|
||||
* @memberof catalog.product
|
||||
*
|
||||
* @param {string} identifier Identifier of the product
|
||||
*
|
||||
|
@ -256,8 +267,8 @@ Object.assign(
|
|||
// Intializing URI of the request
|
||||
const uri = "?" + parameters;
|
||||
|
||||
return await core.request(uri).then(
|
||||
(json) => {
|
||||
return await core.request(uri, undefined, "GET").then(
|
||||
async (json) => {
|
||||
if (json) {
|
||||
// Received a JSON-response
|
||||
|
||||
|
@ -267,6 +278,9 @@ Object.assign(
|
|||
json.errors.length > 0
|
||||
) {
|
||||
// Fail (received errors)
|
||||
|
||||
// Exit (fail)
|
||||
reject(json);
|
||||
} else {
|
||||
// Success (not received errors)
|
||||
|
||||
|
@ -277,7 +291,7 @@ Object.assign(
|
|||
core.window.remove();
|
||||
}
|
||||
|
||||
core.modules.connect("telegram").then(() => {
|
||||
core.modules.connect(["cart", "telegram"]).then(async () => {
|
||||
// Imported the telegram module
|
||||
|
||||
if (
|
||||
|
@ -317,7 +331,36 @@ Object.assign(
|
|||
const images = document.createElement("div");
|
||||
images.classList.add("images", "unselectable");
|
||||
|
||||
const button = core.telegram.api.isVisible;
|
||||
for (
|
||||
const [index, uri] of json.product.images["200"]
|
||||
.entries()
|
||||
) {
|
||||
const image = document.createElement("img");
|
||||
image.setAttribute("src", uri);
|
||||
image.setAttribute("data-image-small", uri);
|
||||
image.setAttribute(
|
||||
"data-image-big",
|
||||
json.product.images["800"][index],
|
||||
);
|
||||
image.setAttribute("ondragstart", "return false;");
|
||||
|
||||
images.appendChild(image);
|
||||
}
|
||||
|
||||
let width = 0;
|
||||
let buffer;
|
||||
[...images.children].forEach(
|
||||
(child) => (width += child.offsetWidth +
|
||||
(isNaN(
|
||||
buffer = parseFloat(
|
||||
getComputedStyle(child).marginRight,
|
||||
),
|
||||
)
|
||||
? 0
|
||||
: buffer)),
|
||||
);
|
||||
|
||||
const button = core.telegram.api.isVisibless;
|
||||
|
||||
// блокировка закрытия изображений
|
||||
let images_from;
|
||||
|
@ -331,11 +374,11 @@ Object.assign(
|
|||
event.button === 0
|
||||
) {
|
||||
{
|
||||
let x = event.pageX || event.touches[0].pageX;
|
||||
let y = event.pageY || event.touches[0].pageY;
|
||||
let _x = images_from.pageX ||
|
||||
const x = event.pageX || event.touches[0].pageX;
|
||||
const y = event.pageY || event.touches[0].pageY;
|
||||
const _x = images_from.pageX ||
|
||||
images_from.touches[0].pageX;
|
||||
let _y = images_from.pageY ||
|
||||
const _y = images_from.pageY ||
|
||||
images_from.touches[0].pageY;
|
||||
|
||||
if (
|
||||
|
@ -344,6 +387,17 @@ Object.assign(
|
|||
_y - y < 10 &&
|
||||
_y - y > -10
|
||||
) {
|
||||
// Replacing small images with big images
|
||||
Array.from(images.children).forEach((image) =>
|
||||
image.setAttribute(
|
||||
"src",
|
||||
image.getAttribute("data-image-big"),
|
||||
)
|
||||
);
|
||||
|
||||
// Expanding the "Web App" window
|
||||
core.telegram.api.expand();
|
||||
|
||||
images.classList.add("extend");
|
||||
|
||||
if (button) {
|
||||
|
@ -365,11 +419,11 @@ Object.assign(
|
|||
};
|
||||
|
||||
const _close = (event) => {
|
||||
let x = event.pageX || event.touches[0].pageX;
|
||||
let y = event.pageY || event.touches[0].pageY;
|
||||
let _x = images_from.pageX ||
|
||||
const x = event.pageX || event.touches[0].pageX;
|
||||
const y = event.pageY || event.touches[0].pageY;
|
||||
const _x = images_from.pageX ||
|
||||
images_from.touches[0].pageX;
|
||||
let _y = images_from.pageY ||
|
||||
const _y = images_from.pageY ||
|
||||
images_from.touches[0].pageY;
|
||||
|
||||
if (
|
||||
|
@ -392,6 +446,15 @@ Object.assign(
|
|||
if (width < card.offsetWidth) {
|
||||
images.hotline.stop();
|
||||
}
|
||||
|
||||
// Replacing big images with small images
|
||||
Array.from(images.children).forEach((image) =>
|
||||
image.setAttribute(
|
||||
"src",
|
||||
image.getAttribute("data-image-small"),
|
||||
)
|
||||
);
|
||||
|
||||
images.classList.remove("extend");
|
||||
|
||||
if (button) {
|
||||
|
@ -406,29 +469,6 @@ Object.assign(
|
|||
}
|
||||
};
|
||||
|
||||
const _start = (event) => {
|
||||
if (
|
||||
event.type === "touchstart" ||
|
||||
event.button === 0
|
||||
) {
|
||||
images.removeEventListener("mousedown", _start);
|
||||
images.removeEventListener("touchstart", _start);
|
||||
images.addEventListener("mouseup", _open);
|
||||
images.addEventListener("touchend", _open);
|
||||
}
|
||||
};
|
||||
|
||||
images.addEventListener("mousedown", _start);
|
||||
images.addEventListener("touchstart", _start);
|
||||
|
||||
for (const uri of json.product.images) {
|
||||
const image = document.createElement("img");
|
||||
image.setAttribute("src", uri);
|
||||
image.setAttribute("ondragstart", "return false;");
|
||||
|
||||
images.append(image);
|
||||
}
|
||||
|
||||
const header = document.createElement("p");
|
||||
header.classList.add("header");
|
||||
|
||||
|
@ -477,38 +517,92 @@ Object.assign(
|
|||
cost.classList.add("cost", "currency");
|
||||
cost.innerText = json.product.cost;
|
||||
|
||||
h3.append(name);
|
||||
exit.append(exit_icon);
|
||||
h3.append(exit);
|
||||
card.append(h3);
|
||||
card.append(images);
|
||||
header.append(brand);
|
||||
card.append(header);
|
||||
card.append(description);
|
||||
card.append(compatibility);
|
||||
footer.append(dimensions);
|
||||
footer.append(weight);
|
||||
footer.append(cost);
|
||||
card.append(footer);
|
||||
wrap.append(card);
|
||||
document.body.append(wrap);
|
||||
h3.appendChild(name);
|
||||
exit.appendChild(exit_icon);
|
||||
h3.appendChild(exit);
|
||||
card.appendChild(h3);
|
||||
card.appendChild(images);
|
||||
header.appendChild(brand);
|
||||
card.appendChild(header);
|
||||
card.appendChild(description);
|
||||
card.appendChild(compatibility);
|
||||
footer.appendChild(dimensions);
|
||||
footer.appendChild(weight);
|
||||
footer.appendChild(cost);
|
||||
card.appendChild(footer);
|
||||
wrap.appendChild(card);
|
||||
document.body.appendChild(wrap);
|
||||
|
||||
// Reinitialize parameter
|
||||
core.window = document.getElementById("window");
|
||||
|
||||
let width = 0;
|
||||
let buffer;
|
||||
[...images.children].forEach(
|
||||
(child) => (width += child.offsetWidth +
|
||||
(isNaN(
|
||||
buffer = parseFloat(
|
||||
getComputedStyle(child).marginRight,
|
||||
// Write
|
||||
const add = () => {
|
||||
core.cart.write(
|
||||
undefined,
|
||||
document.getElementById(
|
||||
"product_" + json.product.identifier,
|
||||
),
|
||||
)
|
||||
? 0
|
||||
: buffer)),
|
||||
);
|
||||
|
||||
core.telegram.api.MainButton
|
||||
.setText(json.product.cart.text.added)
|
||||
.setParams({
|
||||
color: "#90be36",
|
||||
has_shine_effect: true,
|
||||
})
|
||||
.offClick(add)
|
||||
.onClick(added);
|
||||
};
|
||||
|
||||
// Delete
|
||||
const added = () => {
|
||||
core.cart.set(
|
||||
undefined,
|
||||
document.getElementById(
|
||||
"product_" + json.product.identifier,
|
||||
),
|
||||
0,
|
||||
);
|
||||
|
||||
core.telegram.api.MainButton
|
||||
.setText(json.product.cart.text.add)
|
||||
.setParams({
|
||||
color: core.telegram.api.themeParams.button_color,
|
||||
// has_shine_effect: json.product.discount > 0,
|
||||
has_shine_effect: false,
|
||||
})
|
||||
.offClick(added)
|
||||
.onClick(add);
|
||||
};
|
||||
|
||||
if (json.product.cart.amount > 0) {
|
||||
// Initializing the "Main Button" of the "Web App" window
|
||||
core.telegram.api.MainButton
|
||||
.setText(json.product.cart.text.added)
|
||||
.setParams({
|
||||
color: "#90be36",
|
||||
has_shine_effect: true,
|
||||
})
|
||||
.onClick(added)
|
||||
.hideProgress()
|
||||
.enable()
|
||||
.show();
|
||||
} else {
|
||||
// Initializing the "Main Button" of the "Web App" window
|
||||
core.telegram.api.MainButton
|
||||
.setText(json.product.cart.text.add)
|
||||
.setParams({
|
||||
color: core.telegram.api.themeParams.button_color,
|
||||
// has_shine_effect: json.product.discount > 0,
|
||||
has_shine_effect: false,
|
||||
})
|
||||
.onClick(add)
|
||||
.hideProgress()
|
||||
.enable()
|
||||
.show();
|
||||
}
|
||||
|
||||
// блокировка закрытия карточки
|
||||
let from;
|
||||
const _from = (event) => (from = event.target);
|
||||
|
@ -516,11 +610,36 @@ Object.assign(
|
|||
wrap.addEventListener("touchstart", _from);
|
||||
|
||||
const remove = (event) => {
|
||||
console.log("BABLO: " + typeof event);
|
||||
if (
|
||||
typeof event === "undefined" ||
|
||||
event.type !== "popstate"
|
||||
) {
|
||||
history.back();
|
||||
|
||||
// Initialize the buffer of URN parameters @todo after opening window add to document.location.search
|
||||
const parameters = new URLSearchParams(
|
||||
document.location.search,
|
||||
);
|
||||
|
||||
if (parameters.get("product") === identifier) {
|
||||
// The previous window with the product card was exactly the same
|
||||
|
||||
console.log("aboba");
|
||||
|
||||
// Deleting product card
|
||||
parameters.delete("product");
|
||||
|
||||
// Writing to the browser history
|
||||
history.pushState(
|
||||
{},
|
||||
json.product.name,
|
||||
"?" + parameters,
|
||||
);
|
||||
}
|
||||
|
||||
// Write parameter to the buffer of URN parameters
|
||||
parameters.set("product", identifier);
|
||||
}
|
||||
|
||||
wrap.remove();
|
||||
|
@ -539,6 +658,17 @@ Object.assign(
|
|||
document.removeEventListener("click", close);
|
||||
document.removeEventListener("touch", close);
|
||||
window.removeEventListener("popstate", remove);
|
||||
|
||||
// Denitializing the "Back Button" of the "Web App" window
|
||||
core.telegram.api.BackButton.hide();
|
||||
core.telegram.api.BackButton.offClick(remove);
|
||||
|
||||
// Deinitializing the "Main Button" of the "Web App" window
|
||||
core.telegram.api.MainButton
|
||||
.offClick(add)
|
||||
.offClick(added)
|
||||
.disable()
|
||||
.hide();
|
||||
};
|
||||
|
||||
const close = (event) => {
|
||||
|
@ -564,9 +694,29 @@ Object.assign(
|
|||
document.addEventListener("touch", close);
|
||||
window.addEventListener("popstate", remove);
|
||||
|
||||
import("../hotline.js").then((hotline) => {
|
||||
// Initializing the "Back Button" of the "Web App" window
|
||||
core.telegram.api.BackButton.show();
|
||||
core.telegram.api.BackButton.onClick(remove);
|
||||
|
||||
setTimeout(() => {
|
||||
import("./hotline.js").then((hotline) => {
|
||||
// Imported the hotline module
|
||||
|
||||
const _start = (event) => {
|
||||
if (
|
||||
event.type === "touchstart" ||
|
||||
event.button === 0
|
||||
) {
|
||||
images.removeEventListener("mousedown", _start);
|
||||
images.removeEventListener("touchstart", _start);
|
||||
images.addEventListener("mouseup", _open);
|
||||
images.addEventListener("touchend", _open);
|
||||
}
|
||||
};
|
||||
|
||||
images.addEventListener("mousedown", _start);
|
||||
images.addEventListener("touchstart", _start);
|
||||
|
||||
images.hotline = new hotline.default(
|
||||
json.product.identfier,
|
||||
images,
|
||||
|
@ -579,6 +729,7 @@ Object.assign(
|
|||
images.hotline.start();
|
||||
}
|
||||
});
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -602,9 +753,12 @@ Object.assign(
|
|||
catalog.search,
|
||||
{
|
||||
/**
|
||||
* Search (system)
|
||||
* @name Search (system)
|
||||
*
|
||||
* Request search in the catalog and render in the core.main
|
||||
* @description
|
||||
* Request search in the catalog and render result in the core.main
|
||||
*
|
||||
* @memberof catalog.search
|
||||
*
|
||||
* @return {Promise} Request to the server
|
||||
*
|
||||
|
@ -627,10 +781,7 @@ Object.assign(
|
|||
if (
|
||||
(typeof value === "string" ||
|
||||
typeof value === "number") &&
|
||||
!(
|
||||
(parameter === "text" && value === null) ||
|
||||
value.length < 3
|
||||
)
|
||||
!(parameter === "text" && value === null)
|
||||
) {
|
||||
// Validated value
|
||||
|
||||
|
@ -655,7 +806,7 @@ Object.assign(
|
|||
// Found the search <input> element
|
||||
|
||||
input.removeAttribute("disabled");
|
||||
input.focus();
|
||||
// input.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -743,7 +894,7 @@ Object.assign(
|
|||
// Writing the search <search> element after the title <h2> element in the <main> element
|
||||
core.main.insertBefore(
|
||||
search,
|
||||
title.nextSibling,
|
||||
title.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the title <h2> element in the <main> element
|
||||
|
@ -814,7 +965,7 @@ Object.assign(
|
|||
// Writing the categories <section> element after the search <search> element in the <main> element
|
||||
core.main.insertBefore(
|
||||
categories,
|
||||
search.nextSibling,
|
||||
search.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the search <search> element in the <main> element
|
||||
|
@ -830,7 +981,7 @@ Object.assign(
|
|||
// Writing the categories <section> element after the title <h2> element in the <main> element
|
||||
core.main.insertBefore(
|
||||
categories,
|
||||
title.nextSibling,
|
||||
title.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the title <h2> element in the <main> element
|
||||
|
@ -900,7 +1051,7 @@ Object.assign(
|
|||
// Writing the filters <section> element after the categories <section> element in the <main> element
|
||||
core.main.insertBefore(
|
||||
filters,
|
||||
categories.nextSibling,
|
||||
categories.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the categories <section> element in the <main> element
|
||||
|
@ -916,7 +1067,7 @@ Object.assign(
|
|||
// Writing the filters <section> element after the search <search> element in the <main> element
|
||||
core.main.insertBefore(
|
||||
filters,
|
||||
search.nextSibling,
|
||||
search.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the search <search> element in the <main> element
|
||||
|
@ -932,7 +1083,7 @@ Object.assign(
|
|||
// Writing the filters <section> element after the title <h2> element in the <main> element
|
||||
core.main.insertBefore(
|
||||
filters,
|
||||
title.nextSibling,
|
||||
title.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the title <h2> element in the <main> element
|
||||
|
@ -999,7 +1150,7 @@ Object.assign(
|
|||
// Writing the sorting <section> element after the filters <section> element in the <main> element
|
||||
core.main.insertBefore(
|
||||
sorting,
|
||||
filters.nextSibling,
|
||||
filters.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the filters <section> element in the <main> element
|
||||
|
@ -1017,7 +1168,7 @@ Object.assign(
|
|||
// Writing the sorting <section> element after the categories <section> element in the <main> element
|
||||
core.main.insertBefore(
|
||||
sorting,
|
||||
categories.nextSibling,
|
||||
categories.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Notnitialized the categories <section> element in the <main> element
|
||||
|
@ -1033,7 +1184,7 @@ Object.assign(
|
|||
// Writing the sorting <section> element after the search <search> element in the <main> element
|
||||
core.main.insertBefore(
|
||||
sorting,
|
||||
search.nextSibling,
|
||||
search.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not nitialized the search <search> element in the <main> element
|
||||
|
@ -1049,7 +1200,7 @@ Object.assign(
|
|||
// Writing the sorting <section> element after the title <h2> element in the <main> element
|
||||
core.main.insertBefore(
|
||||
sorting,
|
||||
title.nextSibling,
|
||||
title.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the title <h2> element in the <main> element
|
||||
|
@ -1117,7 +1268,7 @@ Object.assign(
|
|||
// Writing the products <section> element after the sorting <section> element in the <main> element
|
||||
core.main.insertBefore(
|
||||
products,
|
||||
sorting.nextSibling,
|
||||
sorting.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Initialized the sorting <section> element in the <main> element
|
||||
|
@ -1133,7 +1284,7 @@ Object.assign(
|
|||
// Writing the products <section> element after the filters <section> element in the <main> element
|
||||
core.main.insertBefore(
|
||||
products,
|
||||
filters.nextSibling,
|
||||
filters.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the filters <section> element in the <main> element
|
||||
|
@ -1151,7 +1302,7 @@ Object.assign(
|
|||
// Writing the products <section> element after the categories <section> element in the <main> element
|
||||
core.main.insertBefore(
|
||||
products,
|
||||
categories.nextSibling,
|
||||
categories.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the categories <section> element in the <main> element
|
||||
|
@ -1167,7 +1318,7 @@ Object.assign(
|
|||
// Writing the products <section> element after the search <search> element in the <main> element
|
||||
core.main.insertBefore(
|
||||
products,
|
||||
search.nextSibling,
|
||||
search.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the search <search> element in the <main> element
|
||||
|
@ -1183,7 +1334,7 @@ Object.assign(
|
|||
// Writing the products <section> element after the title <h2> element in the <main> element
|
||||
core.main.insertBefore(
|
||||
products,
|
||||
title.nextSibling,
|
||||
title.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the title <h2> element in the <main> element
|
|
@ -60,7 +60,7 @@ export default function damper(func, timeout = 300, force) {
|
|||
// Normal execution
|
||||
|
||||
// Deleting the force argument
|
||||
if (typeof force === "number") delete args[force - 1];
|
||||
if (typeof force === "number") args.splice(force - 1, force);
|
||||
|
||||
// Writing promise handlers into the arguments variable
|
||||
args.push(resolve, reject);
|
|
@ -1,161 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* @name Delivery
|
||||
*
|
||||
* @description
|
||||
* Implements actions with delivery
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
export default class delivery {
|
||||
/**
|
||||
* @name Type of the program
|
||||
*/
|
||||
static type = "module";
|
||||
|
||||
/**
|
||||
* @name Company
|
||||
*
|
||||
* @description
|
||||
* Choose a delivery company (interface)
|
||||
*
|
||||
* @param {HTNLElement} element Handler element
|
||||
* @param {string} identifier Identifier of the delivery company
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Execution completed with an error?
|
||||
*/
|
||||
static company = (element, identifier, force = false) => {
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.company.damper(element, identifier, force);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.company.system(element, identifier, force);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @name City
|
||||
*
|
||||
* @description
|
||||
* Write name of a city for delivery (interface)
|
||||
*
|
||||
* @param {HTNLInputElement} element Handler element <input>
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Execution completed with an error?
|
||||
*/
|
||||
static company = (element, force = false) => {
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.city.damper(element, identifier, force);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.company.system(element, identifier, force);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
core.modules.connect("damper").then(() => {
|
||||
// Imported the damper module
|
||||
|
||||
Object.assign(
|
||||
delivery.company,
|
||||
{
|
||||
/**
|
||||
* @name Write
|
||||
*
|
||||
* @description
|
||||
* Choose a delivery company (damper)
|
||||
*
|
||||
* @param {HTNLElement} element Handler element
|
||||
* @param {string} identifier Identifier of the delivery company
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
damper: core.damper(
|
||||
(...variables) => delivery.company.system(...variables),
|
||||
300,
|
||||
3,
|
||||
),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Object.assign(
|
||||
delivery.company,
|
||||
{
|
||||
/**
|
||||
* @name Write
|
||||
*
|
||||
* @description
|
||||
* Choose a delivery company (system)
|
||||
*
|
||||
* @param {string} identifier Identifier of the delivery company
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
async system(
|
||||
type,
|
||||
value,
|
||||
resolve = () => {},
|
||||
reject = () => {},
|
||||
) {
|
||||
try {
|
||||
if (
|
||||
typeof type === "string" &&
|
||||
(typeof value === "string" || typeof valye === "number")
|
||||
) {
|
||||
// Received and validated required arguments
|
||||
|
||||
// Sending request to the server
|
||||
return await core.buffer.write(`delivery_${type}`, value)
|
||||
.then(
|
||||
(json) => {
|
||||
try {
|
||||
|
||||
|
||||
// Exit (success)
|
||||
resolve(json);
|
||||
} catch (e) {
|
||||
// Exit (fail)
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
() => reject(),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// Exit (fail)
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Connecting to the core
|
||||
if (!core.delivery) core.delivery = delivery;
|
|
@ -0,0 +1,898 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* @name Delivery
|
||||
*
|
||||
* @description
|
||||
* Implements actions with delivery
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
export default class delivery {
|
||||
/**
|
||||
* @name Type of the program
|
||||
*/
|
||||
static type = "module";
|
||||
|
||||
/**
|
||||
* @name Company (interface)
|
||||
*
|
||||
* @description
|
||||
* Choose a delivery company
|
||||
*
|
||||
* @param {HTMLInputElement} company The button for choose a delivery company <input> (radio)
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Did the execution complete without errors?
|
||||
*/
|
||||
static company = (company, force = false) => {
|
||||
if (company instanceof HTMLInputElement) {
|
||||
// Initialized the delivery company <input> (radio) element
|
||||
|
||||
// Disabling the delivery company <input> (radio) element
|
||||
company.setAttribute("disabled", true);
|
||||
|
||||
// Initializing timer of enabling the delivery company <input> (radio) element
|
||||
const enabling = setTimeout(() => {
|
||||
// Enabling the delivery company <input> (radio) element
|
||||
company.removeAttribute("disabled");
|
||||
}, 3000);
|
||||
|
||||
// Initializing the order <button> element
|
||||
const order = document.getElementById("order");
|
||||
|
||||
if (order instanceof HTMLButtonElement) {
|
||||
// Initializeg the order <button> element
|
||||
|
||||
// Disabling the order <button> element
|
||||
order.setAttribute("disabled", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Resolved
|
||||
*
|
||||
* @description
|
||||
* Render the result of the resolved request
|
||||
*
|
||||
* @param {object} json
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
const resolved = (json) => {
|
||||
if (json) {
|
||||
// Received JSON-response
|
||||
|
||||
// Deinitializing timer of enabling the delivery company <input> (radio) element
|
||||
clearTimeout(enabling);
|
||||
|
||||
// Enabling the delivery company <input> (radio) element
|
||||
company.removeAttribute("disabled");
|
||||
|
||||
if (json.status === "success") {
|
||||
// Received "success" status of execution
|
||||
|
||||
// Select the delivery company <input> (radio) element
|
||||
company.checked = true;
|
||||
}
|
||||
|
||||
if (json.ready) {
|
||||
// Received readiness status
|
||||
|
||||
if (order instanceof HTMLButtonElement) {
|
||||
// Initializeg the order <button> element
|
||||
|
||||
// Enabling the order <button> element
|
||||
order.removeAttribute("disabled");
|
||||
}
|
||||
|
||||
// Calculating delivery
|
||||
this.calculate();
|
||||
}
|
||||
|
||||
if (json.company) {
|
||||
// Received company property
|
||||
|
||||
// Initializing the delivery company <input> (radio) element
|
||||
const button = document.getElementById(json.company);
|
||||
|
||||
if (button instanceof HTMLInputElement) {
|
||||
// Initialized the delivery company <input> (radio) element
|
||||
|
||||
// Select the delivery company <input> (radio) element
|
||||
button.checked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @name Rejected
|
||||
*
|
||||
* @description
|
||||
* Render the result of the rejected request
|
||||
*
|
||||
* @param {object} json
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
const rejected = (json) => {
|
||||
if (json) {
|
||||
// Received JSON-response
|
||||
|
||||
// Deinitializing timer of enabling the delivery company <input> (radio) element
|
||||
clearTimeout(enabling);
|
||||
|
||||
// Enabling the delivery company <input> (radio) element
|
||||
company.removeAttribute("disabled");
|
||||
}
|
||||
};
|
||||
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.write.damper("company", company.getAttribute("id"), force)
|
||||
.then(resolved, rejected);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.write.system("company", company.getAttribute("id"), force)
|
||||
.then(resolved, rejected);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @name Location (interface)
|
||||
*
|
||||
* @description
|
||||
* Write name of the location for delivery
|
||||
*
|
||||
* @param {HTMLInputElement} location The input element with name of the location <input>
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Did the execution complete without errors?
|
||||
*/
|
||||
static location = (location, force = false) => {
|
||||
if (location instanceof HTMLInputElement) {
|
||||
// Initialized the location <input> element
|
||||
|
||||
// Deleting statuses from the location <input> element
|
||||
location.classList.remove("success", "fail");
|
||||
|
||||
// Initializing the order <button> element
|
||||
const order = document.getElementById("order");
|
||||
|
||||
if (order instanceof HTMLButtonElement) {
|
||||
// Initializeg the order <button> element
|
||||
|
||||
// Disabling the order <button> element
|
||||
order.setAttribute("disabled", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Resolved
|
||||
*
|
||||
* @description
|
||||
* Render the result of the resolved request
|
||||
*
|
||||
* @param {object} json
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
const resolved = (json) => {
|
||||
if (json) {
|
||||
// Received JSON-response
|
||||
|
||||
if (json.status) {
|
||||
// Received status of execution
|
||||
|
||||
// Deleting statuses from the location <input> element
|
||||
location.classList.remove("success", "fail");
|
||||
|
||||
// Writing status of execution as CSS class
|
||||
location.classList.add(json.status);
|
||||
}
|
||||
|
||||
if (json.ready) {
|
||||
// Received readiness status
|
||||
|
||||
if (order instanceof HTMLButtonElement) {
|
||||
// Initializeg the order <button> element
|
||||
|
||||
// Enabling the order <button> element
|
||||
order.removeAttribute("disabled");
|
||||
}
|
||||
|
||||
// Calculating delivery
|
||||
this.calculate();
|
||||
}
|
||||
|
||||
if (json.location) {
|
||||
// Received location
|
||||
|
||||
if (json.location.identifier) {
|
||||
// Received location identifier
|
||||
|
||||
if (location.value !== json.location) {
|
||||
// The value entered differs from what was written on the server
|
||||
|
||||
// Writing attribute with location identifier
|
||||
/* location.setAttribute(
|
||||
"data-location-identifier",
|
||||
json.location.identifier,
|
||||
); */
|
||||
|
||||
// Writing CSS variable with location identifier
|
||||
/* location.style.setProperty(
|
||||
"--identifier",
|
||||
json.location.identifier,
|
||||
); */
|
||||
}
|
||||
}
|
||||
|
||||
if (json.location.input) {
|
||||
// Received location value for <input> element
|
||||
|
||||
if (location.value !== json.location.input) {
|
||||
// The value entered differs from what was written on the server
|
||||
|
||||
// Writing location property to the location <input> element
|
||||
location.value = json.location.input;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (json.locations) {
|
||||
// Reveived locations suggestions
|
||||
|
||||
// Initializing the locations suggestions <datalist> element
|
||||
let locations = document.getElementById("locations");
|
||||
|
||||
if (!(locations instanceof HTMLDataListElement)) {
|
||||
// Not initialized the locations suggestions <datalist> element
|
||||
|
||||
// Initializing the locations suggestions <datalist> element
|
||||
const datalist = document.createElement("datalist");
|
||||
datalist.setAttribute("id", "locations");
|
||||
|
||||
// Connection the locations suggestions <datalist> element to the location <input> element
|
||||
location.setAttribute("list", "locations");
|
||||
|
||||
// Writing the locations suggestions <datalist> element after the location <input> element
|
||||
location.parentElement.insertBefore(
|
||||
datalist,
|
||||
location.nextElementSibling,
|
||||
);
|
||||
|
||||
// Reinitializing the locations suggestions <datalist> element
|
||||
locations = document.getElementById("locations");
|
||||
}
|
||||
}
|
||||
|
||||
if (locations instanceof HTMLDataListElement) {
|
||||
// Initialized locations suggestions <datalist> element
|
||||
|
||||
// Initializing buffer of created options
|
||||
const options = new Set();
|
||||
|
||||
for (const location of json.locations ?? []) {
|
||||
// Iterating over locations suggestions
|
||||
|
||||
// Initializing the location <option> element
|
||||
const option = document.createElement("option");
|
||||
option.setAttribute(
|
||||
"value",
|
||||
(location.name + ", " + location.structure.reverse().join(", "))
|
||||
.trim(),
|
||||
);
|
||||
|
||||
// Writing to buffer of created options
|
||||
options.add(option);
|
||||
}
|
||||
|
||||
// Replacing content of the locations suggestions <datalist> element
|
||||
locations.replaceChildren(...Array.from(options));
|
||||
|
||||
// locations.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @name Rejected
|
||||
*
|
||||
* @description
|
||||
* Render the result of the rejected request
|
||||
*
|
||||
* @param {object} json
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
const rejected = (json) => {
|
||||
if (json) {
|
||||
// Received JSON-response
|
||||
|
||||
if (json.status) {
|
||||
// Reveived status of execution
|
||||
|
||||
// Deleting statuses from the location <input> element
|
||||
location.classList.remove("success", "fail");
|
||||
|
||||
// Writing status of execution as CSS class
|
||||
location.classList.add(json.status);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.write.damper("location", location.value, force)
|
||||
.then(resolved, rejected);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.write.system("location", location.value, force)
|
||||
.then(resolved, rejected);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @name Street (interface)
|
||||
*
|
||||
* @description
|
||||
* Write name of the street for delivery
|
||||
*
|
||||
* @param {HTMLInputElement} element The input element with name of the street <input>
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Did the execution complete without errors?
|
||||
*/
|
||||
static street = (street, force = false) => {
|
||||
if (street instanceof HTMLInputElement) {
|
||||
// Initialized the street <input> element
|
||||
|
||||
// Deleting statuses from the street <input> element
|
||||
street.classList.remove("success", "fail");
|
||||
|
||||
// Initializing the order <button> element
|
||||
const order = document.getElementById("order");
|
||||
|
||||
if (order instanceof HTMLButtonElement) {
|
||||
// Initializeg the order <button> element
|
||||
|
||||
// Disabling the order <button> element
|
||||
order.setAttribute("disabled", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Resolved
|
||||
*
|
||||
* @description
|
||||
* Render the result of the resolved request
|
||||
*
|
||||
* @param {object} json
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
const resolved = (json) => {
|
||||
if (json) {
|
||||
// Received JSON-response
|
||||
|
||||
if (json.status) {
|
||||
// Reveived status of execution
|
||||
|
||||
// Deleting statuses from the street <input> element
|
||||
street.classList.remove("success", "fail");
|
||||
|
||||
// Writing status of execution as CSS class
|
||||
street.classList.add(json.status);
|
||||
}
|
||||
|
||||
if (json.ready) {
|
||||
// Received readiness status
|
||||
|
||||
if (order instanceof HTMLButtonElement) {
|
||||
// Initializeg the order <button> element
|
||||
|
||||
// Enabling the order <button> element
|
||||
order.removeAttribute("disabled");
|
||||
}
|
||||
|
||||
core.modules.connect("telegram").then(() => {
|
||||
// Imported the telegram module
|
||||
|
||||
core.telegram.api.HapticFeedback.notificationOccurred("success");
|
||||
});
|
||||
|
||||
// Calculating delivery
|
||||
this.calculate();
|
||||
}
|
||||
|
||||
if (json.street) {
|
||||
// Received street property
|
||||
|
||||
if (street.value !== json.street) {
|
||||
// The value entered differs from what was written on the server
|
||||
|
||||
// Writing street property to the street element <input>
|
||||
street.value = json.street;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @name Rejected
|
||||
*
|
||||
* @description
|
||||
* Render the result of the rejected request
|
||||
*
|
||||
* @param {object} json
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
const rejected = (json) => {
|
||||
if (json) {
|
||||
// Received JSON-response
|
||||
|
||||
if (json.status) {
|
||||
// Reveived status of execution
|
||||
|
||||
// Deleting statuses from the street <input> element
|
||||
street.classList.remove("success", "fail");
|
||||
|
||||
// Writing status of execution as CSS class
|
||||
street.classList.add(json.status);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.write.damper("street", street.value, force)
|
||||
.then(resolved, rejected);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.write.system("street", street.value, force)
|
||||
.then(resolved, rejected);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @name Write (interface)
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
static write = () => {};
|
||||
|
||||
/**
|
||||
* @name Calculate (interface)
|
||||
*
|
||||
* @description
|
||||
* Calculate delivery by validated data from buffers
|
||||
*
|
||||
* @return {bool} Did the execution complete without errors?
|
||||
*/
|
||||
static calculate = () => {
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.calculate.damper();
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.calculate.system();
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
core.modules.connect("damper").then(() => {
|
||||
// Imported the damper module
|
||||
|
||||
Object.assign(
|
||||
delivery.write,
|
||||
{
|
||||
/**
|
||||
* @name Write (damper)
|
||||
*
|
||||
* @description
|
||||
* Validate and write a delivery parameter into buffers
|
||||
*
|
||||
* @memberof delivery.write
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @param {string|number} value Value of the parameter
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
damper: core.damper(
|
||||
(...variables) => delivery.write.system(...variables),
|
||||
1200,
|
||||
3,
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
Object.assign(
|
||||
delivery.calculate,
|
||||
{
|
||||
/**
|
||||
* @name Calculate (damper)
|
||||
*
|
||||
* @description
|
||||
* Calculate delivery by validated data from buffers
|
||||
*
|
||||
* @memberof delivery.calculate
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
damper: core.damper(
|
||||
(...variables) => delivery.calculate.system(...variables),
|
||||
300,
|
||||
),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Object.assign(
|
||||
delivery.write,
|
||||
{
|
||||
/**
|
||||
* @name Write (system)
|
||||
*
|
||||
* @description
|
||||
* Validate and write a delivery parameter into buffers
|
||||
*
|
||||
* @memberof delivery.write
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @param {string|number} value Value of the parameter
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
async system(
|
||||
name,
|
||||
value,
|
||||
resolve = () => {},
|
||||
reject = () => {},
|
||||
) {
|
||||
try {
|
||||
if (
|
||||
typeof name === "string" &&
|
||||
(typeof value === "string" || typeof value === "number")
|
||||
) {
|
||||
// Received and validated required arguments
|
||||
|
||||
// Sending request to the server
|
||||
return await core.request(
|
||||
"/delivery/write",
|
||||
`${name}=${value}`,
|
||||
"PATCH",
|
||||
)
|
||||
.then(
|
||||
(json) => {
|
||||
if (json) {
|
||||
// Received a JSON-response
|
||||
|
||||
if (
|
||||
json.errors !== null &&
|
||||
typeof json.errors === "object" &&
|
||||
json.errors.length > 0
|
||||
) {
|
||||
// Fail (received errors)
|
||||
|
||||
// Exit (fail)
|
||||
reject(json);
|
||||
} else {
|
||||
// Success (not received errors)
|
||||
|
||||
if (json.validated) {
|
||||
// Validated delivery data from all fields
|
||||
|
||||
// Unblocking the "continue" button in the summary <section> element
|
||||
document.getElementById("summary")
|
||||
?.classList.remove("disabled");
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
resolve(json);
|
||||
}
|
||||
}
|
||||
},
|
||||
() => reject(),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// Exit (fail)
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
Object.assign(
|
||||
delivery.calculate,
|
||||
{
|
||||
/**
|
||||
* @name Calculate (system)
|
||||
*
|
||||
* @description
|
||||
* Calculate delivery by validated data from buffers
|
||||
*
|
||||
* @memberof delivery.calculate
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
async system(
|
||||
resolve = () => {},
|
||||
reject = () => {},
|
||||
) {
|
||||
try {
|
||||
// Sending request to the server
|
||||
return await core.request(
|
||||
"/delivery/calculate",
|
||||
undefined,
|
||||
"GET",
|
||||
)
|
||||
.then(
|
||||
(json) => {
|
||||
if (json) {
|
||||
// Received JSON-response
|
||||
|
||||
if (json.cost) {
|
||||
// Received delivery cost
|
||||
|
||||
// Initializing the delivery cost <span> element
|
||||
const cost = document.getElementById("cost_delivery");
|
||||
|
||||
if (cost instanceof HTMLElement) {
|
||||
// Initialized the delivery cost <span> element
|
||||
|
||||
// Writing delivery cost to the delivery cost <span> element
|
||||
cost.innerText = json.cost;
|
||||
} else {
|
||||
// Not initialized the delivery cost <span> element
|
||||
|
||||
// Initializing the delivery cost <span> element
|
||||
const cost = document.createElement("span");
|
||||
cost.setAttribute("id", "cost_delivery");
|
||||
cost.classList.add(
|
||||
"cost",
|
||||
"delivery",
|
||||
"currency",
|
||||
"plus",
|
||||
"hint",
|
||||
);
|
||||
|
||||
// Initializing the products cost <span> element
|
||||
const products = document.getElementById("cost");
|
||||
|
||||
if (products instanceof HTMLElement) {
|
||||
// Initialized the products cost <span> element
|
||||
|
||||
// Writing the delivery cost <span> element after the products cost <span> element
|
||||
products.parentElement.insertBefore(
|
||||
cost,
|
||||
products.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the products cost <span> element
|
||||
|
||||
// Initializing the products amount <span> element
|
||||
const amount = document.getElementById("cost");
|
||||
|
||||
if (amount instanceof HTMLElement) {
|
||||
// Initialized the products amount <span> element
|
||||
|
||||
// Writing the delivery cost <span> element after the products amount <span> element
|
||||
amount.parentElement.insertBefore(
|
||||
cost,
|
||||
amount.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the products amount <span> element
|
||||
|
||||
// Initializing the order <button> element
|
||||
const order = document.getElementById("cost");
|
||||
|
||||
if (order instanceof HTMLButtonElement) {
|
||||
// Initialized the order <button> element
|
||||
|
||||
// Writing the delivery cost <span> element before the order <button> element
|
||||
order.parentElement.insertBefore(
|
||||
cost,
|
||||
order,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the order <button> element
|
||||
|
||||
// Initializing the summary first row <div> element
|
||||
const summary = core.main.querySelector(
|
||||
"#summary>div.row",
|
||||
);
|
||||
|
||||
if (summary instanceof HTMLElement) {
|
||||
// Initialized the summary first row <div> element
|
||||
|
||||
// Writing the delivery cost <span> element into the summary first row <div> element
|
||||
summary.appendChild(cost);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Writing delivery cost to the delivery cost <span> element
|
||||
cost.innerText = json.cost;
|
||||
}
|
||||
} else {
|
||||
// Not received delivery cost
|
||||
|
||||
// Deleting the delivery cost <span> element
|
||||
document.getElementById("cost_delivery")?.remove();
|
||||
}
|
||||
|
||||
if (json.days) {
|
||||
// Received delivery days
|
||||
|
||||
// Initializing the delivery days <span> element
|
||||
const days = document.getElementById("shipping");
|
||||
|
||||
if (days instanceof HTMLElement) {
|
||||
// Initialized the delivery days <span> element
|
||||
|
||||
// Writing delivery days to the delivery days <span> element
|
||||
days.innerText = json.days;
|
||||
} else {
|
||||
// Not initialized the delivery days <span> element
|
||||
|
||||
// Initializing the delivery days <span> element
|
||||
const days = document.createElement("span");
|
||||
days.setAttribute("id", "shipping");
|
||||
days.classList.add("delivery", "days", "hint");
|
||||
|
||||
// Initializing the delivery cost <span> element
|
||||
const cost = document.getElementById("cost_delivery");
|
||||
|
||||
if (cost instanceof HTMLElement) {
|
||||
// Initialized the delivery cost <span> element
|
||||
|
||||
// Writing the delivery days <span> element after the delivery cost <span> element
|
||||
cost.parentElement.insertBefore(
|
||||
days,
|
||||
cost.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the delivery cost <span> element
|
||||
|
||||
// Initializing the products cost <span> element
|
||||
const products = document.getElementById("cost");
|
||||
|
||||
if (products instanceof HTMLElement) {
|
||||
// Initialized the products cost <span> element
|
||||
|
||||
// Writing the delivery days <span> element after the products cost <span> element
|
||||
products.parentElement.insertBefore(
|
||||
days,
|
||||
products.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the products cost <span> element
|
||||
|
||||
// Initializing the products amount <span> element
|
||||
const amount = document.getElementById("cost");
|
||||
|
||||
if (amount instanceof HTMLElement) {
|
||||
// Initialized the products amount <span> element
|
||||
|
||||
// Writing the delivery days <span> element after the products amount <span> element
|
||||
amount.parentElement.insertBefore(
|
||||
days,
|
||||
amount.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the products amount <span> element
|
||||
|
||||
// Initializing the order <button> element
|
||||
const order = document.getElementById("cost");
|
||||
|
||||
if (order instanceof HTMLButtonElement) {
|
||||
// Initialized the order <button> element
|
||||
|
||||
// Writing the delivery days <span> element before the order <button> element
|
||||
order.parentElement.insertBefore(
|
||||
days,
|
||||
order,
|
||||
);
|
||||
} else {
|
||||
// Not initialized the order <button> element
|
||||
|
||||
// Initializing the summary first row <div> element
|
||||
const summary = core.main.querySelector(
|
||||
"#summary>div.row",
|
||||
);
|
||||
|
||||
if (summary instanceof HTMLElement) {
|
||||
// Initialized the summary first row <div> element
|
||||
|
||||
// Writing the delivery days <span> element into the summary first row <div> element
|
||||
summary.appendChild(days);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Writing delivery days to the delivery days <span> element
|
||||
days.innerText = json.days;
|
||||
}
|
||||
} else {
|
||||
// Not received delivery days
|
||||
|
||||
// Deleting the delivery days <span> element
|
||||
document.getElementById("shipping")?.remove();
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
resolve(json);
|
||||
}
|
||||
},
|
||||
() => reject(),
|
||||
);
|
||||
} catch (e) {
|
||||
// Exit (fail)
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Connecting to the core
|
||||
if (!core.delivery) core.delivery = delivery;
|
0
mirzaev/arming_bot/system/public/js/hotline.js → mirzaev/arming_bot/system/public/js/modules/hotline.js
Normal file → Executable file
0
mirzaev/arming_bot/system/public/js/hotline.js → mirzaev/arming_bot/system/public/js/modules/hotline.js
Normal file → Executable file
|
@ -31,7 +31,7 @@ export default class loader {
|
|||
.request(
|
||||
uri,
|
||||
body,
|
||||
"POST",
|
||||
"GET",
|
||||
{
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "application/json",
|
||||
|
@ -48,6 +48,9 @@ export default class loader {
|
|||
json.errors.length > 0
|
||||
) {
|
||||
// Fail (received errors)
|
||||
|
||||
// Exit (fail)
|
||||
reject(json);
|
||||
} else {
|
||||
// Success (not received errors)
|
||||
|
||||
|
@ -147,7 +150,7 @@ export default class loader {
|
|||
// Writing the <header> element after the last <section> element inside the <body> element
|
||||
body.insertBefore(
|
||||
core.header,
|
||||
section.nextSibling,
|
||||
section.nextElementSibling,
|
||||
);
|
||||
} else {
|
||||
// Not found section elements <section> inside the <body> element
|
||||
|
@ -200,7 +203,7 @@ export default class loader {
|
|||
// Writing the <main> element after the <header> element
|
||||
body.insertBefore(
|
||||
core.main,
|
||||
core.header.nextSibling,
|
||||
core.header.nextElementSibling,
|
||||
);
|
||||
} else if (core.footer instanceof HTMLElement) {
|
||||
// Fount the <footer> element
|
||||
|
@ -219,7 +222,7 @@ export default class loader {
|
|||
// Found the last <section> element inside the <body> element
|
||||
|
||||
// Writing the <main> element after the last <section> element inside the <body> element
|
||||
body.insertBefore(core.main, section.nextSibling);
|
||||
body.insertBefore(core.main, section.nextElementSibling);
|
||||
} else {
|
||||
// Not found section elements <section> inside the <body> element
|
||||
|
|
@ -29,7 +29,7 @@ export default class session {
|
|||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {bool} Execution completed with an error?
|
||||
* @return {bool} Did the execution complete without errors?
|
||||
*/
|
||||
static write = (name, value, force = false) => {
|
||||
core.modules.connect("damper").then(
|
||||
|
@ -48,7 +48,7 @@ export default class session {
|
|||
);
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -60,10 +60,12 @@ core.modules.connect("damper").then(() => {
|
|||
session.buffer.write,
|
||||
{
|
||||
/**
|
||||
* @name Write
|
||||
* @name Write (damper)
|
||||
*
|
||||
* @description
|
||||
* Write to the session buffer (damper)
|
||||
* Write to the session buffer
|
||||
*
|
||||
* @memberof session.buffer.write
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
|
@ -84,10 +86,12 @@ Object.assign(
|
|||
session.buffer.write,
|
||||
{
|
||||
/**
|
||||
* @name Write
|
||||
* @name Write (system)
|
||||
*
|
||||
* @description
|
||||
* Write to the session buffer (system)
|
||||
* Write to the session buffer
|
||||
*
|
||||
* @memberof session.buffer.write
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
|
@ -100,23 +104,39 @@ Object.assign(
|
|||
resolve = () => {},
|
||||
reject = () => {},
|
||||
) {
|
||||
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(
|
||||
"/session/write",
|
||||
`${name}=${value}`,
|
||||
"POST",
|
||||
"PATCH",
|
||||
)
|
||||
.then(
|
||||
(json) => {
|
||||
if (json) {
|
||||
// Received a JSON-response
|
||||
|
||||
if (
|
||||
json.errors !== null &&
|
||||
typeof json.errors === "object" &&
|
||||
json.errors.length > 0
|
||||
) {
|
||||
// Fail (received errors)
|
||||
|
||||
// Exit (fail)
|
||||
reject(json);
|
||||
} else {
|
||||
// Success (not received errors)
|
||||
|
||||
// Exit (success)
|
||||
resolve(json);
|
||||
}
|
||||
}
|
||||
},
|
||||
() => reject(),
|
||||
);
|
|
@ -0,0 +1,47 @@
|
|||
"use strict";
|
||||
|
||||
core.modules.connect(["telegram"])
|
||||
.then(() => {
|
||||
//
|
||||
|
||||
// Expanding the "Web App" window
|
||||
// core.telegram.api.expand();
|
||||
|
||||
// Writing settings for the "Web App" window
|
||||
core.telegram.api.enableVerticalSwipes();
|
||||
core.telegram.api.disableClosingConfirmation();
|
||||
core.telegram.api.setHeaderColor(core.telegram.api.themeParams.header_bg_color);
|
||||
core.telegram.api.setBackgroundColor(core.telegram.api.themeParams.secondary_bg_color);
|
||||
core.telegram.api.setBottomBarColor(core.telegram.api.themeParams.bottom_bar_bg_color);
|
||||
|
||||
// Writing settings for the "Back Button" of the "Web App" window
|
||||
core.telegram.api.BackButton.hide();
|
||||
|
||||
if (core.telegram.api.themeParams.section_bg_color === core.telegram.api.themeParams.bg_color) {
|
||||
// The sections color matches the background color
|
||||
|
||||
// Replacing the sections color with the secondary background color
|
||||
document.documentElement.style.setProperty('--tg-theme-section-bg-color', 'var(--tg-theme-secondary-bg-color)');
|
||||
}
|
||||
|
||||
core.modules.connect(["session", "account"])
|
||||
.then(() => {
|
||||
//
|
||||
|
||||
const { initData, initDataUnsafe, ...data } = core.telegram.api;
|
||||
|
||||
//
|
||||
core.session.buffer.write("telegram_program", JSON.stringify(data));
|
||||
|
||||
|
||||
if (core.telegram.api.initData.length > 0) {
|
||||
//
|
||||
|
||||
//
|
||||
core.account.authentication();
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
core.telegram.api.ready();
|
||||
});
|
|
@ -7,14 +7,15 @@ namespace mirzaev\arming_bot;
|
|||
// Files of the project
|
||||
use mirzaev\arming_bot\controllers\core as controller,
|
||||
mirzaev\arming_bot\models\core as model,
|
||||
mirzaev\arming_bot\models\cart,
|
||||
mirzaev\arming_bot\models\telegram;
|
||||
|
||||
// Фреймворк Telegram
|
||||
// Framework for Telegram
|
||||
use Zanzara\Zanzara,
|
||||
Zanzara\Context,
|
||||
Zanzara\Config;
|
||||
|
||||
ini_set('error_reporting', E_ALL);
|
||||
ini_set('error_reporting', E_ALL ^ E_DEPRECATED);
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
|
||||
|
@ -50,7 +51,7 @@ require __DIR__ . DIRECTORY_SEPARATOR
|
|||
. 'autoload.php';
|
||||
|
||||
// Инициализация ядра контроллеров MINIMAL
|
||||
new controller(false);
|
||||
/* new controller(new core, false); */
|
||||
|
||||
// Инициализация ядра моделей MINIMAL
|
||||
new model(true);
|
||||
|
@ -61,10 +62,26 @@ $config->useReactFileSystem(true);
|
|||
|
||||
$bot = new Zanzara(TELEGRAM_KEY, $config);
|
||||
|
||||
/* $bot->onUpdate(function (Context $ctx): void {
|
||||
var_dump($ctx->getMessage()->getWebAppData());
|
||||
var_dump($ctx->getEffectiveUser() );
|
||||
}); */
|
||||
$bot->onUpdate(function (Context $ctx): void {
|
||||
// Initializing 'web app" data
|
||||
$app = $ctx->getMessage()?->getWebAppData();
|
||||
|
||||
if (!empty($app)) {
|
||||
// Initialized "web app" data
|
||||
|
||||
// Initializing request from "web app" data
|
||||
$request = json_decode($app->getData(), false, 10);
|
||||
|
||||
if ($request->type === 'cart_share') {
|
||||
// Cart attaching
|
||||
|
||||
// Attaching cart to the Telegram account
|
||||
telegram::cart_attach($ctx, $request->hash, $request->delivery);
|
||||
}
|
||||
}
|
||||
|
||||
unset($app);
|
||||
});
|
||||
|
||||
$bot->onCommand('start', fn($ctx) => telegram::start($ctx));
|
||||
$bot->onCommand('contacts', fn($ctx) => telegram::contacts($ctx));
|
||||
|
@ -82,6 +99,10 @@ $bot->onCbQueryData(['import_request'], fn($ctx) => telegram::import_request($ct
|
|||
$bot->onCbQueryData(['tuning'], fn($ctx) => telegram::tuning($ctx));
|
||||
$bot->onCbQueryData(['brands'], fn($ctx) => telegram::brands($ctx));
|
||||
|
||||
$bot->onException(function (Context $ctx, $exception) {
|
||||
var_dump($exception);
|
||||
});
|
||||
|
||||
// Инициализация middleware с обработкой аккаунта
|
||||
$bot->middleware([telegram::class, "account"]);
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ main>section:is(#summary, #products, #delivery) {
|
|||
main>section#delivery>div.column {
|
||||
padding: 1rem;
|
||||
gap: var(--gap);
|
||||
background-color: var(--tg-theme-secondary-bg-color);
|
||||
background-color: var(--tg-theme-section-bg-color);
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#deliveries>input {
|
||||
|
@ -19,10 +19,11 @@ main>section#delivery>div.column>div#deliveries>input {
|
|||
main>section#delivery>div.column>div#deliveries>input+label {
|
||||
/* backdrop-filter: brightness(1.2); */
|
||||
/* background-color: unset; */
|
||||
filter: brightness(0.6);
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#deliveries>input+label:is(:hover, :focus) {
|
||||
filter: brightness(1.1) hue-rotate(30deg);
|
||||
filter: brightness(1.3);
|
||||
/* filter: unset; */
|
||||
/* backdrop-filter: brightness(1.4); */
|
||||
}
|
||||
|
@ -33,15 +34,15 @@ main>section#delivery>div.column>div#deliveries>input+label:active {
|
|||
}
|
||||
|
||||
main>section#delivery>div.column>div#deliveries>input:checked+label {
|
||||
filter: hue-rotate(30deg);
|
||||
filter: unset;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#deliveries>input:checked+label:is(:hover, :focus) {
|
||||
filter: brightness(1.1) hue-rotate(30deg);
|
||||
filter: brightness(1.5);
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#deliveries>input:checked+label:active {
|
||||
filter: brightness(0.9) hue-rotate(30deg);
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#address {
|
||||
|
@ -49,7 +50,7 @@ main>section#delivery>div.column>div#address {
|
|||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column>div#address>input#city {
|
||||
main>section#delivery>div.column>div#address>input#location {
|
||||
min-width: max(6rem, 20%);
|
||||
width: min(6rem, 100%);
|
||||
flex-grow: 1;
|
||||
|
@ -61,6 +62,10 @@ main>section#delivery>div.column>div#address>input#street {
|
|||
flex-grow: 10;
|
||||
}
|
||||
|
||||
main>section#delivery>div.column:not(:has(div#deliveries>input:checked))>div#address {
|
||||
display: none;
|
||||
}
|
||||
|
||||
main:has(section#summary.disabled:hover)>section#delivery>div {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
@ -74,7 +79,7 @@ main>section#summary>div {
|
|||
overflow: hidden;
|
||||
pointer-events: auto;
|
||||
border-radius: 1.375rem;
|
||||
background-color: var(--tg-theme-secondary-bg-color);
|
||||
background-color: var(--tg-theme-section-bg-color);
|
||||
}
|
||||
|
||||
main>section#summary>div>span {
|
||||
|
@ -89,6 +94,11 @@ main>section#summary>div>button#order {
|
|||
/* margin-left: 0.3rem; */
|
||||
margin-left: auto;
|
||||
padding-left: 0.7rem;
|
||||
background: unset;
|
||||
}
|
||||
|
||||
main>section#summary>div>button#order * {
|
||||
color: var(--tg-theme-button-color);
|
||||
}
|
||||
|
||||
main>section#summary.disabled,
|
||||
|
@ -112,7 +122,7 @@ main>section#products>article.product {
|
|||
display: flex;
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
background-color: var(--tg-theme-secondary-bg-color);
|
||||
background-color: var(--tg-theme-section-bg-color);
|
||||
}
|
||||
|
||||
main>section#products>article.product[data-product-amount]:not([data-product-amount="0"]) * {
|
||||
|
@ -132,11 +142,15 @@ main>section#products>article.product>a {
|
|||
}
|
||||
|
||||
main>section#products>article.product>a>img:first-of-type {
|
||||
width: 5rem;
|
||||
/* width: 5rem; */
|
||||
min-width: 5rem;
|
||||
min-height: 100%;
|
||||
object-fit: cover;
|
||||
image-rendering: auto;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: -5px 0px 50px 10px rgba(0, 0, 0, 0.4);
|
||||
-webkit-box-shadow: -5px 0px 50px 10px rgba(0, 0, 0, 0.4);
|
||||
-moz-box-shadow: -5px 0px 50px 10px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
main>section#products>article.product>div {
|
||||
|
@ -215,8 +229,12 @@ main>section#products>article.product>div>div.footer>input {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
@container summary (max-width: 350px) {
|
||||
main>section#summary>div>span:not(#cost) {
|
||||
@container summary (max-width: 450px) {
|
||||
main>section#summary>div>span.days {
|
||||
--days: var(--days-short);
|
||||
}
|
||||
|
||||
main>section#summary>div>span:not(#cost, #cost_delivery, #shipping) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,10 @@ main>section#categories>a.category[type="button"]:has(>img)>p {
|
|||
margin: unset;
|
||||
width: min-content;
|
||||
border-radius: 0.75rem;
|
||||
background: var(--tg-theme-secondary-bg-color);
|
||||
background: var(--tg-theme-section-bg-color);
|
||||
box-shadow: 0px 0px 8px 4px rgba(0, 0, 0, 0.3);
|
||||
-webkit-box-shadow: 0px 0px 8px 4px rgba(0, 0, 0, 0.3);
|
||||
-moz-box-shadow: 0px 0px 8px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
main>section#categories>a.category[type="button"]>p {
|
||||
|
@ -95,13 +98,14 @@ main>section#products>div.column>article.product {
|
|||
flex-direction: column;
|
||||
border-radius: 0.75rem;
|
||||
overflow: clip;
|
||||
backdrop-filter: brightness(0.7);
|
||||
cursor: pointer;
|
||||
backdrop-filter: brightness(0.7);
|
||||
background-color: var(--tg-theme-section-bg-color);
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product:is(:hover, :focus) {
|
||||
/* flex-grow: 0.1; */
|
||||
/* background-color: var(--tg-theme-secondary-bg-color); */
|
||||
/* background-color: var(--tg-theme-section-bg-color); */
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product:is(:hover, :focus)>* {
|
||||
|
@ -112,14 +116,13 @@ main>section#products>div.column>article.product:not(:is(:hover, :focus))>* {
|
|||
transition: 0.2s ease-out;
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product>a {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product>a>img:first-of-type {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
image-rendering: auto;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0px 5px 70px 20px rgba(0, 0, 0, 0.3);
|
||||
-webkit-box-shadow: 0px 5px 70px 20px rgba(0, 0, 0, 0.3);
|
||||
-moz-box-shadow: 0px 5px 70px 20px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product>a>img:first-of-type+* {
|
||||
|
@ -129,13 +132,15 @@ main>section#products>div.column>article.product>a>img:first-of-type+* {
|
|||
main>section#products>div.column>article.product>a>p.title {
|
||||
z-index: 50;
|
||||
margin: unset;
|
||||
padding: 4px 8px;
|
||||
padding: 4px 8px 8px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: bold;
|
||||
overflow-wrap: anywhere;
|
||||
hyphens: auto;
|
||||
color: var(--tg-theme-text-color);
|
||||
background-color: var(--tg-theme-secondary-bg-color);
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product>a>p.title>span {
|
||||
color: var(--tg-theme-hint-color);
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product>div[data-product="buttons"]:last-of-type {
|
||||
|
@ -144,6 +149,8 @@ main>section#products>div.column>article.product>div[data-product="buttons"]:las
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
main>section#products>div.column>article.product[data-product-amount]:not(:is([data-product-amount="0"], [data-product-amount="1"]))>div[data-product="buttons"]:last-of-type {
|
||||
|
|
|
@ -10,6 +10,7 @@ i.icon.close {
|
|||
border: 2px solid transparent;
|
||||
border-radius: 40px;
|
||||
}
|
||||
|
||||
i.icon.close::after,
|
||||
i.icon.close::before {
|
||||
content: "";
|
||||
|
@ -24,7 +25,7 @@ i.icon.close::before {
|
|||
top: 8px;
|
||||
left: 1px;
|
||||
}
|
||||
|
||||
i.icon.close::after {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,14 +10,25 @@ section[data-type="select"] {
|
|||
height: var(--height-close);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
cursor: context-menu;
|
||||
cursor: pointer;
|
||||
border-radius: 0.75rem;
|
||||
overflow-x: hidden;
|
||||
background-color: var(--tg-theme-button-color);
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
section[data-type="select"]:not(:focus):after {
|
||||
section[data-type="select"]>button:has(>i.icon.close) {
|
||||
align-self: end;
|
||||
height: 100%;
|
||||
padding: 0 0.4rem;
|
||||
}
|
||||
|
||||
section[data-type="select"]:has(input[id$="title"]:checked)>button:has(>i.icon.close),
|
||||
section[data-type="select"]:focus>button:has(>i.icon.close) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
section[data-type="select"]:not(:focus, :has(>button>i.icon.close)):after {
|
||||
z-index: 30;
|
||||
content: '';
|
||||
top: calc(50% - 2.5px);
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
--socket-connected: #2be851;
|
||||
--socket-disconnected: #8e8181;
|
||||
--socket-text: #b09999;
|
||||
|
||||
outline: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
* {
|
||||
|
@ -31,19 +34,23 @@ body {
|
|||
margin: 0;
|
||||
min-height: 100vh;
|
||||
padding: 0;
|
||||
gap: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: clip;
|
||||
overflow-x: hidden;
|
||||
background-color: var(--tg-theme-bg-color);
|
||||
}
|
||||
|
||||
body:has(section#window) {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
aside {}
|
||||
|
||||
header {
|
||||
container-type: inline-size;
|
||||
container-name: header;
|
||||
margin: 2rem 0 1rem;
|
||||
margin-top: 2rem;
|
||||
padding: 0 var(--offset-x);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -54,6 +61,7 @@ header {
|
|||
main {
|
||||
container-type: inline-size;
|
||||
container-name: main;
|
||||
flex-grow: 1;
|
||||
padding: 0 var(--offset-x);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -62,10 +70,6 @@ main {
|
|||
transition: 0s;
|
||||
}
|
||||
|
||||
main>section:last-child {
|
||||
margin-bottom: 5rem;
|
||||
}
|
||||
|
||||
main>*[data-section] {
|
||||
width: var(--width);
|
||||
}
|
||||
|
@ -74,6 +78,24 @@ main>section[data-section]>p {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
main>article {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
main>article>:is(h2, h2 + small) {
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
main>article>h2+small {
|
||||
margin-bottom: 1.8rem;
|
||||
color: var(--tg-theme-hint-color);
|
||||
}
|
||||
|
||||
main>article>h3 {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
main>search {
|
||||
--gap: 16px;
|
||||
--border-width: 1px;
|
||||
|
@ -88,6 +110,51 @@ main>search {
|
|||
|
||||
footer {}
|
||||
|
||||
footer>section#govno {
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
gap: 0.2rem;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
footer>section#govno>p {
|
||||
margin: unset;
|
||||
display: inline-flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: center;
|
||||
gap: 0 0.4rem;
|
||||
}
|
||||
|
||||
footer>section#govno>span#company {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
footer>section#govno>p>span#identifier {
|
||||
color: var(--tg-theme-hint-color);
|
||||
}
|
||||
|
||||
footer>section#govno>p>span#tax {
|
||||
color: var(--tg-theme-hint-color);
|
||||
}
|
||||
|
||||
footer>section#govno>p>span#identifier:before {
|
||||
content: var(--company-identifier) ':';
|
||||
margin-right: 0.1rem;
|
||||
color: var(--tg-theme-text-color);
|
||||
}
|
||||
|
||||
footer>section#govno>p>span#tax:before {
|
||||
content: var(--company-tax) ':';
|
||||
margin-right: 0.1rem;
|
||||
color: var(--tg-theme-text-color);
|
||||
}
|
||||
|
||||
footer>section#govno>a:last-child {
|
||||
margin-top: 0.4rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
search:has(input:is(:focus, :active)) {
|
||||
border-color: var(--tg-theme-accent-text-color);
|
||||
transition: unset;
|
||||
|
@ -180,6 +247,16 @@ input {
|
|||
margin-left: var(--currency-offset, 0.1rem);
|
||||
}
|
||||
|
||||
.cost.plus:before {
|
||||
content: '+';
|
||||
margin-left: var(--plus-offset, 0.1rem);
|
||||
}
|
||||
|
||||
.delivery.days:after {
|
||||
content: var(--days);
|
||||
margin-left: var(--days-offset, 0.1rem);
|
||||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
@ -194,6 +271,19 @@ input {
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.success {
|
||||
/* color: var(--tg-theme-hint-color, green); */
|
||||
color: #697d30;
|
||||
}
|
||||
|
||||
.fail {
|
||||
color: var(--tg-theme-destructive_text_color, red);
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: var(--tg-theme-hint-color);
|
||||
}
|
||||
|
||||
.unselectable {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
|
|
|
@ -8,7 +8,18 @@ section#window {
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
backdrop-filter: brightness(50%) contrast(120%) grayscale(60%) blur(1.2px);
|
||||
/* backdrop-filter: brightness(40%) contrast(120%) grayscale(60%) blur(1.2px); */
|
||||
}
|
||||
|
||||
section#window:before {
|
||||
content: '';
|
||||
z-index: -100;
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
opacity: 0.7;
|
||||
background: #000;
|
||||
filter: brightness(50%);
|
||||
}
|
||||
|
||||
section#window>div.card {
|
||||
|
@ -60,8 +71,10 @@ section#window>div.card>h3>a.exit[type="button"] {
|
|||
section#window>div.card>div.images {
|
||||
--image-width: 10rem;
|
||||
height: 10rem;
|
||||
min-height: 10rem;
|
||||
max-height: 10rem;
|
||||
display: flex;
|
||||
overflow: clip;
|
||||
overflow: hidden;
|
||||
cursor: zoom-in;
|
||||
transition: 0s;
|
||||
}
|
||||
|
@ -70,35 +83,43 @@ section#window>div.card>div.images.extend {
|
|||
--image-width: max(30rem, 40vw);
|
||||
z-index: 9999999;
|
||||
left: 0;
|
||||
top: 0;
|
||||
top: 10vh;
|
||||
margin: unset !important;
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
max-width: unset;
|
||||
height: 100vh;
|
||||
height: 80vh;
|
||||
min-height: 80vh;
|
||||
max-height: 80vh;
|
||||
align-items: center;
|
||||
cursor: normal;
|
||||
cursor: default;
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
section#window>div.card:has(>div.images.extend)>:not(div.images.extend) {
|
||||
filter: brightness(50%);
|
||||
}
|
||||
|
||||
section#window>div.card>div.images>img {
|
||||
margin-right: 0.5rem;
|
||||
width: var(--image-width);
|
||||
max-width: var(--image-width);
|
||||
height: 100%;
|
||||
width: auto;
|
||||
height: 10rem;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
object-fit: cover;
|
||||
object-fit: contain;
|
||||
image-rendering: auto;
|
||||
border-radius: 0.5rem;
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
section#window>div.card>div.images.extend>img {
|
||||
margin-right: 2vw;
|
||||
height: max-content;
|
||||
object-fit: unset;
|
||||
border-radius: unset;
|
||||
margin-right: 3vw;
|
||||
width: auto;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
section#window>div.card>div.images>img:last-child {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
*
|
||||
!deliveries/
|
||||
!.gitignore
|
||||
!*.sample
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'account' => '',
|
||||
'secret' => ''
|
||||
];
|
|
@ -23,15 +23,22 @@ use Twig\Loader\FilesystemLoader,
|
|||
|
||||
|
||||
// Built-in libraries
|
||||
use ArrayAccess;
|
||||
use ArrayAccess as array_access,
|
||||
Error as error;
|
||||
|
||||
/**
|
||||
* Templater core
|
||||
* Templater
|
||||
*
|
||||
* @package mirzaev\arming_bot\views
|
||||
* @author ${REPO_OWNER} < mail >
|
||||
*
|
||||
* @param
|
||||
*
|
||||
* @method
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class templater extends controller implements ArrayAccess
|
||||
final class templater extends controller implements array_access
|
||||
{
|
||||
/**
|
||||
* Registry of global variables of view
|
||||
|
@ -73,9 +80,12 @@ final class templater extends controller implements ArrayAccess
|
|||
$this->twig->addGlobal('language', $language = $account?->language ?? $session?->buffer['language'] ?? $settings?->language ?? language::en);
|
||||
$this->twig->addGlobal('currency', $currency = $account?->currency ?? $session?->buffer['currency'] ?? $settings?->currency ?? currency::usd);
|
||||
|
||||
// FUCKING SAFARI
|
||||
/* $this->twig->addGlobal('safari', str_contains(strtolower($_SERVER['HTTP_USER_AGENT']), 'safari')); */
|
||||
|
||||
// @todo move functions into controllers
|
||||
|
||||
// Initialize function of dimensions formatting
|
||||
// Initializing function of dimensions formatting
|
||||
$this->twig->addFunction(
|
||||
new TwigFunction(
|
||||
'format_dimensions',
|
||||
|
@ -105,6 +115,57 @@ final class templater extends controller implements ArrayAccess
|
|||
)
|
||||
);
|
||||
|
||||
// Initializing filter for formatting string to numbers only
|
||||
$this->twig->addFilter(
|
||||
new TwigFilter(
|
||||
'numbers_only',
|
||||
function (string|null $string = null) {
|
||||
// Universalizing SIM-number
|
||||
preg_match_all('/\d/', $string, $numbers);
|
||||
$universalized = implode($numbers[0]);
|
||||
|
||||
// Deinitializing unnecessary variables(
|
||||
unset($numbers);
|
||||
|
||||
// Exit (success)
|
||||
return $universalized;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Initializing filter for formatting SIM-numbers string to E.164
|
||||
$this->twig->addFilter(
|
||||
new TwigFilter(
|
||||
'sim_format',
|
||||
function (string|int|float|null $sim = null) {
|
||||
// Universalizing SIM-number
|
||||
preg_match_all('/\d/', $sim, $numbers);
|
||||
$universalized = implode($numbers[0]);
|
||||
|
||||
// Deinitializing unnecessary variables(
|
||||
unset($numbers);
|
||||
|
||||
// Parsing parts of SIM-number
|
||||
preg_match('/^(\d)(\d{3})(\d{1,11})$/', $universalized, $parsed);
|
||||
unset($parsed[0]);
|
||||
|
||||
// Deinitializing unnecessary variables(
|
||||
unset($universalized);
|
||||
|
||||
try {
|
||||
// Generating formatted SIM-number by E.164
|
||||
$formatted = sprintf('+%u %s %s', ...$parsed);
|
||||
|
||||
// Exit (success)
|
||||
return $formatted;
|
||||
} catch (error $e) {
|
||||
// Exit (fail)
|
||||
return $sim;
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Initializing of twig extensions
|
||||
$this->twig->addExtension(new intl());
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
{% block body %}
|
||||
{% if account is empty %}
|
||||
<section id="account">
|
||||
<a onclick="core.account.authentication()">
|
||||
<!-- <a onclick="core.account.authentication()">
|
||||
{{ language == 'ru' ? "Аутентификация" : "Authentication" }}
|
||||
</a>
|
||||
</a> -->
|
||||
</section>
|
||||
{% else %}
|
||||
<section id="account">
|
||||
|
@ -19,5 +19,5 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="/js/modules/account.js" type="module"></script>
|
||||
<script src="/js/modules/account.mjs" type="module"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -3,22 +3,22 @@
|
|||
<div class="column">
|
||||
<div id="deliveries" class="row">
|
||||
{% for identifier, delivery in deliveries %}
|
||||
<input id="{{ identifier }}" name="delivery" type="radio" {% if identifier==session.buffer.delivery.company %}
|
||||
<input id="{{ identifier }}" name="delivery" type="radio" {% if identifier == (account.buffer.delivery.company ?? session.buffer.delivery.company) %}
|
||||
checked{% endif %}>
|
||||
<label class="rounded" for="{{ identifier }}" type="button"
|
||||
title="{{ (language.name == 'ru' ? 'Выбрать ' : 'Choose ') ~ delivery.label }}"
|
||||
onclick="core.buffer.write.damper('delivery_company', '{{ identifier }}').then(() => core.delivery.check())" tabindex="5">
|
||||
onclick="core.delivery.company(this.previousElementSibling)" tabindex="5">
|
||||
{{ delivery.label }}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div id="address" class="row">
|
||||
<input id="city" placeholder="{{ language.name == 'ru' ? 'Город' : 'City' }}"
|
||||
value="{{ session.buffer.delivery.city ?? account.buffer.delivery.city }}"
|
||||
onkeydown="core.buffer.write.damper('delivery_city', this.value).then(() => core.delivery.check())" tabindex="6" />
|
||||
<input id="street" placeholder="{{ language.name == 'ru' ? 'Улица' : 'Street' }}"
|
||||
value="{{ session.buffer.delivery.street ?? session.buffer.delivery.street }}"
|
||||
onkeydown="core.buffer.write.damper('delivery_street', this.value).then(() => core.delivery.check())" tabindex="6" />
|
||||
<input id="location" class="identifier{% if session.buffer.delivery[account.buffer.delivery.company ?? session.buffer.delivery.company].location.identifier %} success{% endif %}" placeholder="{{ language.name == 'ru' ? 'Город' : 'location' }}"
|
||||
value="{{ session.buffer.delivery[account.buffer.delivery.company ?? session.buffer.delivery.company] ? session.buffer.delivery[account.buffer.delivery.company ?? session.buffer.delivery.company].location.name ~ ', ' ~ (session.buffer.delivery[account.buffer.delivery.company ?? session.buffer.delivery.company].location.structure|reverse|join(', ')) : (session.buffer.delivery.location ?? account.buffer.delivery.location) }}"
|
||||
onkeyup="core.delivery.location(this)" oninput="core.delivery.location(this)" tabindex="6" />
|
||||
<input id="street"{% if session.buffer.delivery[account.buffer.delivery.company ?? session.buffer.delivery.company].street %} class="success"{% endif %} placeholder="{{ language.name == 'ru' ? 'Улица' : 'Street' }}"
|
||||
value="{{ session.buffer.delivery[account.buffer.delivery.company ?? session.buffer.delivery.company] ? session.buffer.delivery[account.buffer.delivery.company ?? session.buffer.delivery.company].street : (session.buffer.delivery.street ?? session.buffer.delivery.street) }}"
|
||||
onkeyup="core.delivery.street(this)" oninput="core.delivery.location(this)" tabindex="6" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
{% macro card(product, amount) %}
|
||||
<article id="{{ product._id }}" class="product" data-product-identifier="{{ product.identifier }}"
|
||||
<article id="product_{{ product.identifier ?? 'FAIL' }}" class="product" data-product-identifier="{{ product.identifier }}"
|
||||
data-product-amount="{{ amount }}" {% if amount> 0 %} style="--hue-rotate-offset: {{ amount }}0deg;"{% endif %}>
|
||||
<a data-product="cover" href="?product={{ product.identifier }}" onclick="return core.catalog.product(this);"
|
||||
<a data-product="cover" href="?product={{ product.identifier }}" onclick="return !core.catalog.product(this);"
|
||||
onkeydown="event.keyCode === 13 && core.catalog.product(this)" tabindex="10">
|
||||
<img src="{{ product.images.0.storage }}" alt="{{ product.name }}" ondrugstart="return false;">
|
||||
<img src="{{ product.images.0.storage.200 }}" alt="{{ product.name }}" ondragstart="return false;">
|
||||
</a>
|
||||
<div>
|
||||
<div class="head" title="{{ product.name }}">
|
||||
{{ product.name | length > 65 ? product.name | slice(0, 65) ~ '...' : product.name }}
|
||||
<!-- <button data-product-button="list" onclick="core.cart.list(this, document.getElementById('{{ product._id }}'))" title="{{ language.name == 'ru' ? 'Добавить в список' : 'Add to a list' }}"><i class="icon small list add"></i></button> -->
|
||||
<!-- <button data-product-button="list" onclick="core.cart.list(this, document.getElementById('product_{{ product.identifier ?? 'FAIL' }}'))" title="{{ language.name == 'ru' ? 'Добавить в список' : 'Add to a list' }}"><i class="icon small list add"></i></button> -->
|
||||
<button data-product-button="toggle"
|
||||
onclick="core.cart.toggle(this, document.getElementById('{{ product._id }}'), true)"
|
||||
onclick="core.cart.toggle(this, document.getElementById('product_{{ product.identifier ?? 'FAIL' }}'), true)"
|
||||
title="{{ language.name == 'ru' ? 'Удалить' : 'Delete' }}"><i class="icon small trash"></i></button>
|
||||
</div>
|
||||
<div class="body">
|
||||
|
@ -26,14 +26,14 @@
|
|||
<div class="footer">
|
||||
<span class="cost currency" data-product-parameter="cost">{{ product.cost }}</span>
|
||||
<button data-product-button="delete"
|
||||
onclick="core.cart.delete(this, document.getElementById('{{ product._id }}'), 1)"
|
||||
onclick="core.cart.delete(this, document.getElementById('product_{{ product.identifier ?? 'FAIL' }}'), 1)"
|
||||
title="{{ language.name == 'ru' ? 'Уменьшить' : 'Decrease' }}"><i class="icon small minus"></i></button>
|
||||
<input type="text" value="{{ amount ?? 1 }}" title="{{ language.name == 'ru' ? 'Количество' : 'Amount' }}"
|
||||
data-product-parameter="amount"
|
||||
onchange="core.cart.set(this, document.getElementById('{{ product._id }}'), +this.value)"
|
||||
onchange="core.cart.set(this, document.getElementById('product_{{ product.identifier ?? 'FAIL' }}'), +this.value)"
|
||||
oninput="this.value = (this.value = +this.value.replaceAll(/[^\d]/g, '')) > 100 ? 100 : (this.value < 0 ? 0 : this.value)"></input>
|
||||
<button data-product-button="write"
|
||||
onclick="core.cart.write(this, document.getElementById('{{ product._id }}'), 1)"
|
||||
onclick="core.cart.write(this, document.getElementById('product_{{ product.identifier ?? 'FAIL' }}'), 1)"
|
||||
title="{{ language.name == 'ru' ? 'Увеличить' : 'Increase' }}"><i class="icon small plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
{% if cart.products is not empty %}
|
||||
<section id="summary"
|
||||
class="column {% if delivery is empty or delivery.city is empty or delivery.street is empty %}disabled{% endif %} unselectable"
|
||||
{% if delivery is empty %}title="{{ language.name == " ru" ? "Выберите тип доставки" : "Choose delivery type" }}"{%
|
||||
endif %}>
|
||||
class="column unselectable">
|
||||
<div class="row">
|
||||
<span id="amount">{{ cart.summary.amount ?? 0 }}</span>
|
||||
<span>{{ language.name == "ru" ? "товаров на сумму" : "products worth" }}</span>
|
||||
<span id="cost" class="cost currency">{{ cart.summary.cost ?? 0 }}</span>
|
||||
<button id="order" onclick="core.cart.order(this)" {% if delivery is empty %} disabled="true" {% endif %}
|
||||
{% if session.buffer.delivery[account.buffer.delivery.company ?? session.buffer.delivery.company].cost %}
|
||||
<span id="cost_delivery" class="cost delivery currency plus hint">{{ session.buffer.delivery[account.buffer.delivery.company ?? session.buffer.delivery.company].cost }}</span>
|
||||
{% endif %}
|
||||
{% if session.buffer.delivery[account.buffer.delivery.company ?? session.buffer.delivery.company].days %}
|
||||
<span id="shipping" class="delivery days hint">{{ session.buffer.delivery[account.buffer.delivery.company ?? session.buffer.delivery.company].days }}</span>
|
||||
{% endif %}
|
||||
<button id="order" onclick="core.cart.share(this)" {% if session.buffer.delivery[account.buffer.delivery.company ??
|
||||
session.buffer.delivery.company].location is empty or session.buffer.delivery[account.buffer.delivery.company ??
|
||||
session.buffer.delivery.company].street is empty %} disabled="true" {% endif %}
|
||||
title="{{ language.name == 'ru' ? 'Оформить заказ' : 'Place an order' }}"><i class="icon arrow"></i></button>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
<script src="/js/modules/delivery.js" type="module"></script>
|
||||
<script src="/js/modules/cart.js" type="module"></script>
|
||||
<script type="text/javascript" src="https://auth.robokassa.ru/Merchant/bundle/robokassa_iframe.js"></script>
|
||||
<script src="/js/modules/delivery.mjs" type="module"></script>
|
||||
<script src="/js/modules/cart.mjs" type="module"></script>
|
||||
<script src="/js/modules/hotline.js" type="module"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
{% if categories is not empty %}
|
||||
<section id="categories" class="unselectable">
|
||||
{% for category in categories %}
|
||||
<a id="{{ category.getId() }}" class="category" type="button" href="?category={{ category.identifier }}"
|
||||
onclick="core.catalog.parameters.set('category', {{ category.identifier }}); return core.catalog.search(event);"
|
||||
<a id="category_{{ category.identifier }}" data-category-identifier="{{ category.identifier }}" class="category"
|
||||
type="button" href="?category={{ category.identifier }}"
|
||||
onclick="core.catalog.parameters.set('category', {{ category.identifier }}); return !core.catalog.search(event);"
|
||||
onkeydown="event.keyCode === 13 && (core.catalog.parameters.set('category', {{ category.identifier }}), core.catalog.search(event))"
|
||||
tabindex="3">
|
||||
{% if category.images %}
|
||||
<img src="{{ category.images.0.storage }}" alt="{{ category.name }}" ondrugstart="return false;">
|
||||
<img src="{{ category.images.0.storage.400 }}" alt="{{ category.name }}" ondragstart="return false;">
|
||||
{% endif %}
|
||||
<p>{{ category.name }}</p>
|
||||
</a>
|
||||
|
|
|
@ -3,20 +3,21 @@
|
|||
<section class="unselectable" data-type="select" tabindex="4">
|
||||
{% set buffer_brand = session.buffer.catalog.filters.brand ?? account.buffer.catalog.filters.brand %}
|
||||
<input name="brand" type="radio" id="brand_title" {% if buffer_brand is empty %}checked{% endif %}>
|
||||
<label for="brand_title" type="button" onclick="core.catalog.parameters.set('brand', null); core.catalog.search(event)">
|
||||
{{ language.name == 'ru' ? 'Бренд' : 'Brand' }}
|
||||
</label>
|
||||
<input name="brand" type="radio" id="brand_all">
|
||||
<label for="brand_all" type="button" onclick="core.catalog.parameters.set('brand', null); core.catalog.search(event)">
|
||||
<label for="brand_title" type="button">{{ language.name == 'ru' ? 'Бренд' : 'Brand' }}</label>
|
||||
<!-- <input name="brand" type="radio" id="brand_all" oninput="core.catalog.parameters.set('brand', null); core.catalog.search(event)">
|
||||
<label for="brand_all" type="button">
|
||||
{{ language.name == 'ru' ? 'Все бренды' : 'All brands' }}
|
||||
</label>
|
||||
</label> -->
|
||||
{% for brand in filters.brands %}
|
||||
<input name="brand" type="radio" id="brand_{{ loop.index }}" {% if brand == buffer_brand %} checked{% endif %}>
|
||||
<label for="brand_{{ loop.index }}" type="button"
|
||||
onclick="core.catalog.parameters.set('brand', '{{ brand }}'); core.catalog.search(event)">
|
||||
<input name="brand" type="radio" id="brand_{{ loop.index }}"
|
||||
oninput="core.catalog.parameters.set('brand', '{{ brand }}'); core.catalog.search(event)" {% if
|
||||
brand==buffer_brand %}checked{% endif %}>
|
||||
<label for="brand_{{ loop.index }}" type="button">
|
||||
{{ brand }}
|
||||
</label>
|
||||
{% endfor %}
|
||||
<button onclick="core.catalog.parameters.set('brand', null); core.catalog.search(event)"><i
|
||||
class="icon small close"></i></button>
|
||||
</section>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
|
|
@ -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 %}
|
||||
<article id="{{ product.getId() }}" class="product unselectable" data-product-identifier="{{ product.identifier }}"
|
||||
{% set amount = cart.products[product.getId()].amount ?? 0 %}
|
||||
<article id="product_{{ product.identifier ?? 'FAIL' }}" class="product unselectable" data-product-identifier="{{ product.identifier }}"
|
||||
data-product-amount="{{ amount }}"{% if amount > 0 %} style="--hue-rotate-offset: {{ amount }}0deg;"{% endif %}>
|
||||
<a data-product="cover" href="?product={{ product.identifier }}" onclick="return core.catalog.product(this);"
|
||||
<a data-product="cover" href="?product={{ product.identifier }}" onclick="return !core.catalog.product(this);"
|
||||
onkeydown="event.keyCode === 13 && core.catalog.product(this)" tabindex="10">
|
||||
<img src="{{ product.images.0.storage }}" alt="{{ product.name }}" ondrugstart="return false;">
|
||||
<p class="title" title="{{ product.name }}">
|
||||
{{ title | length > 45 ? title | slice(0, 45) ~ '...' : title }}
|
||||
<img src="{{ product.images.0.storage.400 }}" alt="{{ product.name }}" ondragstart="return false;">
|
||||
<p class="title" title="{{ product.name }} {{ title }}">
|
||||
{{ product.name }}
|
||||
<span>{{ title | length > 45 ? title | slice(0, 45) ~ '...' : title }}</span>
|
||||
</p>
|
||||
</a>
|
||||
<div data-product="buttons">
|
||||
<button data-product-button="delete" onclick="core.cart.delete(this, document.getElementById('{{ product.getId() }}'), 1)" title="{{ language.name == 'ru' ? 'Уменьшить' : 'Decrease' }}"><i class="icon small minus"></i></button>
|
||||
<button data-product-button="toggle" onclick="core.cart.toggle(this, document.getElementById('{{ product.getId() }}'))" tabindex="15">
|
||||
<button data-product-button="delete" onclick="core.cart.delete(this, document.getElementById('product_{{ product.identifier ?? 'FAIL' }}'), 1)" title="{{ language.name == 'ru' ? 'Уменьшить' : 'Decrease' }}"><i class="icon small minus"></i></button>
|
||||
<button data-product-button="toggle" onclick="core.cart.toggle(this, document.getElementById('product_{{ product.identifier ?? 'FAIL' }}'))" tabindex="15">
|
||||
<span data-product-parameter="amount">{{ amount }}</span>
|
||||
<span class="cost currency" data-product-parameter="cost">{{ product.cost }}</span>
|
||||
</button>
|
||||
<button data-product-button="write" onclick="core.cart.write(this, document.getElementById('{{ product.getId() }}'), 1)" title="{{ language.name == 'ru' ? 'Увеличить' : 'Increase' }}"><i class="icon small plus"></i></button>
|
||||
<button data-product-button="write" onclick="core.cart.write(this, document.getElementById('product_{{ product.identifier ?? 'FAIL' }}'), 1)" title="{{ language.name == 'ru' ? 'Увеличить' : 'Increase' }}"><i class="icon small plus"></i></button>
|
||||
</div>
|
||||
</article>
|
||||
{% endmacro %}
|
||||
|
@ -26,14 +27,14 @@ product.dimensions.y, product.dimensions.z, ' ') ~ ' ' ~ product.weight ~ 'г' %
|
|||
<div class="column">
|
||||
{% for product in products %}
|
||||
{% if loop.index % 2 == 1 %}
|
||||
{{ _self.card(product) }}
|
||||
{{ _self.card(cart, product) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="column">
|
||||
{% for product in products %}
|
||||
{% if loop.index % 2 == 0 %}
|
||||
{{ _self.card(product) }}
|
||||
{{ _self.card(cart, product) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
<label class="unselectable"><i class="icon search"></i></label>
|
||||
{% set buffer_search = session.buffer.catalog.search.text ?? account.buffer.catalog.search.text %}
|
||||
<input placeholder="{{ language.name == 'ru' ? 'Поиск по каталогу' : 'Search in the catalog' }}" type="search" tabindex="1"
|
||||
onkeyup="event.keyCode === 9 || core.catalog.parameters.set('text', this.value); core.catalog.search(event, this)"
|
||||
onkeyup="event.keyCode === 9 || core.catalog.parameters.set('text', this.value); return core.catalog.search(event, this)"
|
||||
{% if buffer_search is not empty %} value="{{ buffer_search }}" {% endif %} />
|
||||
</search>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
<script src="/js/modules/catalog.js" type="module"></script>
|
||||
<script src="/js/modules/cart.js" type="module"></script>
|
||||
<script src="/js/hotline.js" type="module"></script>
|
||||
<script src="/js/modules/catalog.mjs" type="module"></script>
|
||||
<script src="/js/modules/cart.mjs" type="module"></script>
|
||||
<script src="/js/modules/hotline.js" type="module"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -10,5 +10,5 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="/js/modules/connection.js" type="module"></script>
|
||||
<script src="/js/modules/connection.mjs" type="module"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -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 %}
|
||||
<footer>
|
||||
{{ block('offer_body') }}
|
||||
</footer>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ block('offer_js') }}
|
||||
{% endblock %}
|
||||
|
|
|
@ -12,14 +12,16 @@
|
|||
|
||||
{% block css %}
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/main.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/loading_spinner.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/loading.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/loading_spinner.css" rel="preload" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/loading.css" rel="preload" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/window.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/fonts/dejavu.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/fonts/kabrio.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/fonts/kabrio.css" rel="preload" />
|
||||
<style>
|
||||
:root {
|
||||
--currency: "{{ currency.symbol ?? '$' }}";
|
||||
--days: "{{ language.name == 'ru' ? 'дней' : 'days' }}";
|
||||
--days-short: "{{ language.name == 'ru' ? 'дн' : 'd' }}";
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html lang="ru">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body style="margin: 0;">
|
||||
<script type="text/javascript" src="https://auth.robokassa.ru/Merchant/bundle/robokassa_iframe.js"></script>
|
||||
<script>
|
||||
console.log(Robokassa.StartPayment({
|
||||
MerchantLogin: 'MIRZAEV',
|
||||
OutSum: '{{ summary.cost + cart.buffer.delivery.cost }}',
|
||||
InvId: {{ cart.getKey() }},
|
||||
Description: '{{ language.name == 'ru' ? 'Оплата заказа' : 'Payment for the order' }}',
|
||||
Culture: '{{ language.name }}',
|
||||
Encoding: 'utf-8',
|
||||
Settings: JSON.stringify ({PaymentMethods:['BankCard','SBP'], Mode:'modal'}),
|
||||
SignatureValue: '3925b771e47d405cbcbb492daa936824'
|
||||
}));
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
{% block body %}
|
||||
<!-- {{ block('connection_body') }} -->
|
||||
<!-- {{ block('account_body') }} -->
|
||||
{{ block('account_body') }}
|
||||
{{ block('header') }}
|
||||
<main>
|
||||
{% block main %}
|
||||
|
@ -27,7 +27,7 @@
|
|||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
{{ block('connection_js') }}
|
||||
<!-- {{ block('connection_js') }} -->
|
||||
{{ block('account_js') }}
|
||||
{{ block('header_js') }}
|
||||
{{ block('footer_js') }}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
{% block js %}
|
||||
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
||||
<script src="/js/modules/damper.js" type="module"></script>
|
||||
<script src="/js/modules/damper.mjs" type="module"></script>
|
||||
<script src="/js/core.js"></script>
|
||||
<script src="/js/modules/loader.js" type="module"></script>
|
||||
<script src="/js/modules/telegram.js" type="module"></script>
|
||||
<script src="/js/modules/session.js" type="module"></script>
|
||||
<script src="/js/modules/loader.mjs" type="module"></script>
|
||||
<script src="/js/modules/telegram.mjs" type="module"></script>
|
||||
<script src="/js/modules/session.mjs" type="module"></script>
|
||||
<script src="/js/authentication.js"></script>
|
||||
<script src="/js/telegram.js"></script>
|
||||
{% if javascript is not empty %}
|
||||
<script>
|
||||
core.modules.connect("damper").then(() => {
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
{% endif %}
|
||||
<span>{{ button.name }}</span>
|
||||
{% if button.image.storage %}
|
||||
<img src="{{ button.image.storage }}" alt="{{ button.name }}" ondrugstart="return false;">
|
||||
<img src="{{ button.image.storage }}" alt="{{ button.name }}" ondragstart="return false;">
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
{% block css %}
|
||||
{% if settings.company %}
|
||||
<style>
|
||||
:root {
|
||||
--company-identifier: "{{ language.name == 'ru' ? 'ОГРН' : 'ID' }}";
|
||||
--company-tax: "{{ language.name == 'ru' ? 'ИНН' : 'TAX' }}";
|
||||
}
|
||||
</style>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{% if settings.company %}
|
||||
<section id="govno" class="column">
|
||||
{% if settings.company.name %}
|
||||
<span id="company">{{ settings.company.name }}</span>
|
||||
{% endif %}
|
||||
{% if settings.company.tax and settings.company.identifier %}
|
||||
<p class="row"><span id="tax">{{ settings.company.tax }}</span><span id="identifier">{{ settings.company.identifier }}</span></p>
|
||||
{% endif %}
|
||||
{% if settings.company.offer %}
|
||||
<a href="/offer" onclick="return core.loader.load('/offer');">{{ language.name == 'ru' ? 'Публичная оферта' : 'Public offer'}}</a>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,368 @@
|
|||
{% extends "/themes/default/index.html" %}
|
||||
{% use "/themes/default/header.html" with css as header_css, body as header, js as header_js %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
<style>
|
||||
main>article#offer {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
main>article#offer ol {
|
||||
counter-reset: item;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
main>article#offer ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
main>article#offer>ol {
|
||||
margin: unset;
|
||||
padding: unset;
|
||||
gap: 1.2rem;
|
||||
}
|
||||
|
||||
main>article#offer>ol>li ol {
|
||||
padding: 0 0 0 1rem;
|
||||
}
|
||||
|
||||
main>article#offer>ol>li ul {
|
||||
padding: 0 0 0 1.5rem;
|
||||
}
|
||||
|
||||
main>article#offer>ol>li:before {
|
||||
content: counters(item, ".") ".";
|
||||
counter-increment: item;
|
||||
margin-right: unset;
|
||||
float: left;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
main>article#offer>ol>:is(li, section)>h3 {
|
||||
margin: 0 0 0.6rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
main>article#offer>ol>section>h3:has(+ p) {
|
||||
margin: 0.6rem 0 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
main>article#offer>ol>section>h3+p {
|
||||
margin: unset;
|
||||
}
|
||||
|
||||
main>article#offer>ol>li>:is(ol, ul)>li:has(>h4) {
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
main>article#offer>ol>li>:is(ol, ul)>li>h4 {
|
||||
margin: 0 0 0.4rem;
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
main>article#offer ol>li {
|
||||
display: block;
|
||||
}
|
||||
|
||||
main>article#offer ol>li:before {
|
||||
content: counters(item, ".") ".";
|
||||
counter-increment: item;
|
||||
float: left;
|
||||
}
|
||||
|
||||
main>article#offer :is(ol, ul)>li:before {
|
||||
margin-right: 0.4rem;
|
||||
color: var(--tg-theme-text-color);
|
||||
}
|
||||
|
||||
main>article#offer :is(ol, ul)>li::marker {
|
||||
margin-right: 0.4rem;
|
||||
color: var(--tg-theme-text-color);
|
||||
}
|
||||
|
||||
main>article#offer :is(ol, ul) {
|
||||
color: var(--tg-theme-hint-color);
|
||||
}
|
||||
|
||||
main>article#offer :is(ol, ul)>li {
|
||||
color: var(--tg-theme-hint-color);
|
||||
}
|
||||
|
||||
main>article#offer ol>span.separator {
|
||||
margin-top: 0.4rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{{ block('header') }}
|
||||
<main>
|
||||
<article id="offer">
|
||||
<h2>ПУБЛИЧНАЯ ОФЕРТА</h2>
|
||||
<small>о заключении договора купли-продажи</small>
|
||||
<ol>
|
||||
<li>
|
||||
<h3>Общие положения</h3>
|
||||
<ol>
|
||||
В настоящей Публичной оферте содержатся условия заключения Договора купли-продажи (далее по тексту - «Договор
|
||||
купли-продажи» и/или «Договор»). Настоящей офертой признается предложение, адресованное одному или нескольким
|
||||
конкретным лицам, которое достаточно определенно и выражает намерение лица, сделавшего предложение, считать
|
||||
себя
|
||||
заключившим Договор с адресатом, которым будет принято предложение. Совершение указанных в настоящей Оферте
|
||||
действий
|
||||
является подтверждением согласия обеих Сторон заключить договор купли-продажи на условиях, в порядке и объеме,
|
||||
изложенных в настоящей Оферте. Нижеизложенный текст Публичной оферты является официальным публичным
|
||||
предложением
|
||||
Продавца, адресованный заинтересованному кругу лиц заключить Договор купли-продажи в соответствии с
|
||||
положениями
|
||||
пункта 2 статьи 437 Гражданского кодекса РФ. Договор купли-продажи считается заключенным и приобретает силу с
|
||||
момента совершения Сторонами действий, предусмотренных в настоящей Оферте, и, означающих безоговорочное, а
|
||||
также
|
||||
полное принятие всех условий настоящей оферты без каких-либо изъятий или ограничений на условиях
|
||||
присоединения.
|
||||
<span class="separator">Термины и определения:</span>
|
||||
Договор – текст настоящей Оферты с Приложениями, являющимися неотъемлемой частью настоящей Оферты,
|
||||
акцептованный
|
||||
Покупателем путем совершения конклюдентных действий, предусмотренных настоящей Офертой. Конклюдентные действия
|
||||
-
|
||||
это поведение, которое выражает согласие с предложением контрагента заключить, изменить или расторгнуть
|
||||
договор.
|
||||
Действия состоят в полном или частичном выполнении условий, которые предложил контрагент. Сайт Продавца в сети
|
||||
«Интернет» – совокупность программ для электронных вычислительных машин и иной информации, содержащейся в
|
||||
информационной системе, доступ к которой обеспечивается посредством сети «Интернет» по доменному имени и
|
||||
сетевому адресу: __________ Стороны Договора (Стороны) – Продавец и Покупатель. Товар - товаром по договору
|
||||
купли-продажи
|
||||
могут быть любые вещи с соблюдением правил, предусмотренных статьей 29 Гражданского кодекса РФ.
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
<h3>Предмет Договора</h3>
|
||||
<ol>
|
||||
<li>По настоящему Договору Продавец обязуется передать вещь (Товар) в собственность Покупателя, а Покупатель
|
||||
обязуется принять Товар и уплатить за него определенную денежную сумму.</li>
|
||||
<li>Наименование, количество, а также ассортимент Товара, его стоимость, порядок доставки и иные условия
|
||||
определяются на основании сведений Продавца при оформлении заявки Покупателем, либо устанавливаются на сайте
|
||||
Продавца в сети «Интернет»______</li>
|
||||
<li>
|
||||
<h4>Подтверждение настоящей Оферты выражается в совершении конклюдентных действий, в частности:</h4>
|
||||
<ul>
|
||||
<li>действиях, связанных с регистрацией учетной записи на Сайте Продавца в сети «Интернет» при наличии
|
||||
необходимости регистрации учетной записи</li>
|
||||
<li>путем составления и заполнения заявки на оформление заказа Товара</li>
|
||||
<li>путем сообщения требуемых для заключения Договора сведений по телефону, электронной почте, указанными
|
||||
на
|
||||
сайте Продавца в сети «Интернет», в том числе, при обратном звонке Продавца по заявке Покупателя</li>
|
||||
<li>оплаты Товара Покупателем. Данный перечень не является исчерпывающим, могут быть и другие действия,
|
||||
которые ясно выражают намерение лица принять предложение контрагента.</li>
|
||||
</ul>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
<h3>Права и обязанности Сторон</h3>
|
||||
<ol>
|
||||
<li>
|
||||
<h4>Права и обязанности Продавца</h4>
|
||||
<ol>
|
||||
<li>Продавец вправе требовать оплаты Товаров и их доставки в порядке и на условиях, предусмотренных
|
||||
Договором</li>
|
||||
<li>Отказать в заключении Договора на основании настоящей Оферты Покупателю в случае его
|
||||
недобросовестного поведения, в частности, в случае</li>
|
||||
<ul>
|
||||
<li>более 2 (Двух) отказов от Товаров надлежащего качества в течение года</li>
|
||||
<li>предоставления заведомо недостоверной персональной информации</li>
|
||||
<li>возврата испорченного Покупателем Товара или Товара, бывшего в употреблении</li>
|
||||
<li>иных случаях недобросовестного поведения, свидетельствующих о заключении Покупателем Договора с
|
||||
целью
|
||||
злоупотребления правами, и отсутствии обычной экономической цели Договора - приобретения Товара.</li>
|
||||
</ul>
|
||||
<li>Продавец обязуется передать Покупателю Товар надлежащего качества и в надлежащей упаковке</li>
|
||||
<li>Передать Товар свободным от прав третьих лиц</li>
|
||||
<li>Организовать доставку Товаров Покупателю</li>
|
||||
<li>Предоставить Покупателю всю необходимую информацию в соответствии с требованиями действующего
|
||||
законодательства РФ и настоящей Оферты</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Права и обязанности Покупателя:</h4>
|
||||
<ol>
|
||||
<li>Покупатель вправе требовать передачи Товара в порядке и на условиях, предусмотренным Договором</li>
|
||||
<li>Требовать предоставления всей необходимой информации в соответствии с требованиями действующего
|
||||
законодательства РФ и настоящей Оферты</li>
|
||||
<li>Отказаться от Товара по основаниям, предусмотренным Договором и действующим законодательством
|
||||
Российской Федерации</li>
|
||||
<li>Покупатель обязуется предоставить Продавцу достоверную информацию, необходимую для надлежащего
|
||||
исполнения Договора</li>
|
||||
<li>Принять и оплатить Товар в соответствии с условиями Договора</li>
|
||||
<li>Покупатель гарантирует, что все условия Договора ему понятны; Покупатель принимает условия без
|
||||
оговорок,
|
||||
а также в полном объеме</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
<h3>Цена и порядок расчетов</h3>
|
||||
<ol>
|
||||
<li>Стоимость, а также порядок оплаты Товара определяется на основании сведений Продавца при оформлении
|
||||
заявки Покупателем, либо устанавливаются на сайте Продавца в сети «Интернет»: ____</li>
|
||||
<li>Все расчеты по Договору производятся в безналичном порядке.</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
<h3>Обмен и возврат Товара</h3>
|
||||
<ol>
|
||||
<li>Покупатель вправе осуществить возврат (обмен) Продавцу Товара, приобретенный дистанционным способом, за
|
||||
исключением перечня товаров, не подлежащих обмену и возврату согласно действующему законодательству
|
||||
Российской Федерации. Условия, сроки и порядок возврата Товара надлежащего и ненадлежащего качества
|
||||
установлены в соответствии с требованиями Гражданского кодекса РФ, Закона РФ от 07.02.1992 N 2300-1 «О
|
||||
защите
|
||||
прав потребителей»; Правил, утвержденных Постановлением Правительства РФ от 31.12.2020 N 2463.</li>
|
||||
<li>Требование Покупателя об обмене либо о возврате Товара подлежит удовлетворению, если Товар не был в
|
||||
употреблении, сохранены его потребительские свойства и имеются доказательства приобретения его у Продавца
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
<h3>Конфиденциальность и безопасность</h3>
|
||||
<ol>
|
||||
<li>При реализации настоящего Договора Стороны обеспечивают конфиденциальность и безопасность персональных
|
||||
данных в соответствии с актуальной редакцией ФЗ от 27.07.2006 г. № 152-ФЗ «О персональных данных» и ФЗ от
|
||||
27.07.2006 г. №149-ФЗ «Об информации, информационных технологиях и о защите информации».</li>
|
||||
<li>Стороны обязуются сохранять конфиденциальность информации, полученной в ходе исполнения настоящего
|
||||
Договора, и принять все возможные меры, чтобы предохранить полученную информацию от разглашения.</li>
|
||||
<li>Под конфиденциальной информацией понимается любая информация, передаваемая Продавцом и Покупателем в
|
||||
процессе реализации Договора и подлежащая защите, исключения указаны ниже.</li>
|
||||
<li>Такая информация может содержаться в предоставляемых Продавцом локальных нормативных актах, договорах,
|
||||
письмах, отчетах, аналитических материалах, результатах исследований, схемах, графиках, спецификациях и
|
||||
других
|
||||
документах, оформленных как на бумажных, так и на электронных носителях.</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
<h3>Форс-мажор</h3>
|
||||
<ol>
|
||||
<li>Стороны освобождаются от ответственности за неисполнение или ненадлежащее исполнение обязательств по
|
||||
Договору, если надлежащее исполнение оказалось невозможным вследствие непреодолимой силы, то есть
|
||||
чрезвычайных
|
||||
и
|
||||
непредотвратимых при данных условиях обстоятельств, под которыми понимаются: запретные действия властей,
|
||||
эпидемии, блокада, эмбарго, землетрясения, наводнения, пожары или другие стихийные бедствия.</li>
|
||||
<li>В случае наступления этих обстоятельств Сторона обязана в течение 30 (Тридцати) рабочих дней уведомить об
|
||||
этом другую Сторону.</li>
|
||||
<li>Документ, выданный уполномоченным государственным органом, является достаточным подтверждением наличия
|
||||
и продолжительности действия непреодолимой силы.</li>
|
||||
<li>Если обстоятельства непреодолимой силы продолжают действовать более 60 (Шестидесяти) рабочих дней, то
|
||||
каждая Сторона вправе отказаться от настоящего Договора в одностороннем порядке.</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
<h3>Ответственность Сторон</h3>
|
||||
<ol>
|
||||
<li>В случае неисполнения и/или ненадлежащего исполнения своих обязательств по Договору, Стороны несут
|
||||
ответственность в соответствии с условиями настоящей Оферты</li>
|
||||
<li>Сторона, не исполнившая или ненадлежащим образом исполнившая обязательства по Договору, обязана
|
||||
возместить другой Стороне причиненные такими нарушениями убытки.</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
<h3>Срок действия настоящей Оферты</h3>
|
||||
<ol>
|
||||
<li>Оферта вступает в силу с момента размещения на Сайте Продавца и действует до момента её отзыва
|
||||
Продавцом.</li>
|
||||
<li>Продавец оставляет за собой право внести изменения в условия Оферты и/или отозвать Оферту в любой момент
|
||||
по своему усмотрению. Сведения об изменении или отзыве Оферты доводятся до Покупателя по выбору Продавца
|
||||
посредством размещения на сайте Продавца в сети «Интернет», в Личном кабинете Покупателя, либо путем
|
||||
направления
|
||||
соответствующего уведомления на электронный или почтовый адрес, указанный Покупателем при заключении
|
||||
Договора
|
||||
или в ходе его исполнения</li>
|
||||
<li>Договор вступает в силу с момента Акцепта условий настоящей Оферты Покупателем и действует до полного
|
||||
исполнения Сторонами обязательств по Договору</li>
|
||||
<li>Изменения, внесенные Продавцом в Договор и опубликованные на сайте в форме актуализированной Оферты,
|
||||
считаются принятыми Покупателем в полном объеме</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
<h3>Дополнительные условия</h3>
|
||||
<ol>
|
||||
<li>Договор, его заключение и исполнение регулируется действующим законодательством Российской Федерации.
|
||||
Все вопросы, не урегулированные настоящей Офертой или урегулированные не полностью, регулируются в
|
||||
соответствии
|
||||
с материальным правом Российской Федерации</li>
|
||||
<li>В случае возникновения спора, который может возникнуть между Сторонами в ходе исполнения ими своих<br />
|
||||
обязательств по Договору, заключенному на условиях настоящей Оферты, Стороны обязаны урегулировать спор
|
||||
мирным
|
||||
путем до начала судебного разбирательства. Судебное разбирательство осуществляется в соответствии с
|
||||
законодательством Российской Федерации. Споры или разногласия, по которым Стороны не достигли
|
||||
договоренности,
|
||||
подлежат разрешению в соответствии с законодательством РФ. Досудебный порядок урегулирования спора является
|
||||
обязательным</li>
|
||||
<li>В качестве языка Договора, заключаемого на условиях настоящей Оферты, а также языка, используемого при
|
||||
любом взаимодействии Сторон (включая ведение переписки, предоставление требований/уведомлений/разъяснений,
|
||||
предоставление документов и т. д.), Стороны определили русский язык</li>
|
||||
<li>Все документы, подлежащие предоставлению в соответствии с условиями настоящей Оферты, должны быть
|
||||
составлены на русском языке либо иметь перевод на русский язык, удостоверенный в установленном порядке</li>
|
||||
<li>Бездействие одной из Сторон в случае нарушения условий настоящей Оферты не лишает права
|
||||
заинтересованной Стороны осуществлять защиту своих интересов позднее, а также не означает отказа от своих
|
||||
прав
|
||||
в случае совершения одной из Сторон подобных либо сходных нарушений в будущем</li>
|
||||
<li>Если на Сайте Продавца в сети «Интернет» есть ссылки на другие веб-сайты и материалы третьих лиц, такие
|
||||
ссылки размещены исключительно в целях информирования, и Продавец не имеет контроля в отношении содержания
|
||||
таких сайтов или материалов. Продавец не несет ответственность за любые убытки или ущерб, которые могут
|
||||
возникнуть в результате использования таких ссылок</li>
|
||||
</ol>
|
||||
</li>
|
||||
<section>
|
||||
<h3>Реквизиты Продавца</h3>
|
||||
<p class="column" style="gap: 0.4rem;">
|
||||
{% if settings.company.name %}
|
||||
<span class="row">
|
||||
<b>Наименование:</b>
|
||||
<span style="margin-left: auto; text-align: right;">{{ settings.company.name }}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if settings.company.tax %}
|
||||
<span class="row">
|
||||
<b>ИНН:</b>
|
||||
<span style="margin-left: auto; text-align: right;">{{ settings.company.tax }}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if settings.company.identifier %}
|
||||
<span class="row">
|
||||
<b>ОГРН/ОГРНИП:</b>
|
||||
<span style="margin-left: auto; text-align: right;">{{ settings.company.identifier }}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if settings.company.sim %}
|
||||
<span class="row">
|
||||
<b>Телефон:</b>
|
||||
<span style="margin-left: auto; text-align: right;">
|
||||
<a href="tel:{{ settings.company.sim|numbers_only }}">{{ settings.company.sim|sim_format }}</a>
|
||||
</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if settings.company.identifier %}
|
||||
<span class="row">
|
||||
<b>Почта:</b>
|
||||
<span style="margin-left: auto; text-align: right;">
|
||||
<a href="mailto:{{ settings.company.mail }}">{{ settings.company.mail }}</a>
|
||||
</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
</section>
|
||||
</ol>
|
||||
</article>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
|
@ -19,6 +19,6 @@
|
|||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
<script src="/js/modules/cart.js" type="module"></script>
|
||||
<script src="/js/modules/hotline.js" type="module"></script>
|
||||
<script src="/js/modules/cart.mjs" type="module"></script>
|
||||
<script src="/js/module/hotline.js" type="module"></script>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue