resolved #129, resolved #124, resolved #123, resolved #119, resolved #118, resolved #114, resolved #111

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2024-05-31 10:50:05 +07:00
parent a5b771a99a
commit 19a5dbd9b7
178 changed files with 4261 additions and 1682 deletions

View File

@ -30,16 +30,17 @@
} }
], ],
"require": { "require": {
"php": "~8.2", "php": "~8.3",
"ext-sodium": "~8.2.4", "ext-sodium": "~8.3",
"mirzaev/minimal": "^2.0.x-dev", "mirzaev/minimal": "^2.0.x-dev",
"mirzaev/accounts": "~1.2.x-dev", "mirzaev/accounts": "~1.2.x-dev",
"mirzaev/arangodb": "^1.0.0", "mirzaev/arangodb": "^1.0.0",
"triagens/arangodb": "~3.9.x-dev", "triagens/arangodb": "~3.9.x-dev",
"twig/twig": "^3.4", "twig/twig": "^3.10",
"twig/extra-bundle": "^3.7", "twig/extra-bundle": "^3.7",
"twig/intl-extra": "^3.7", "twig/intl-extra": "^3.10",
"phpoffice/phpspreadsheet": "^1.29" "phpoffice/phpspreadsheet": "^2.1",
"openswoole/core": "^22.1"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "~9.5" "phpunit/phpunit": "~9.5"

400
composer.lock generated
View File

@ -4,69 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "588f1020cbf90d4c8ed02b057592c14f", "content-hash": "c0be2095032e176b9fb16a24e7a4d1a1",
"packages": [ "packages": [
{
"name": "ezyang/htmlpurifier",
"version": "v4.16.0",
"source": {
"type": "git",
"url": "https://github.com/ezyang/htmlpurifier.git",
"reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/523407fb06eb9e5f3d59889b3978d5bfe94299c8",
"reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8",
"shasum": ""
},
"require": {
"php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0"
},
"require-dev": {
"cerdic/css-tidy": "^1.7 || ^2.0",
"simpletest/simpletest": "dev-master"
},
"suggest": {
"cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.",
"ext-bcmath": "Used for unit conversion and imagecrash protection",
"ext-iconv": "Converts text to and from non-UTF-8 encodings",
"ext-tidy": "Used for pretty-printing HTML"
},
"type": "library",
"autoload": {
"files": [
"library/HTMLPurifier.composer.php"
],
"psr-0": {
"HTMLPurifier": "library/"
},
"exclude-from-classmap": [
"/library/HTMLPurifier/Language/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "Edward Z. Yang",
"email": "admin@htmlpurifier.org",
"homepage": "http://ezyang.com"
}
],
"description": "Standards compliant HTML filter written in PHP",
"homepage": "http://htmlpurifier.org/",
"keywords": [
"html"
],
"support": {
"issues": "https://github.com/ezyang/htmlpurifier/issues",
"source": "https://github.com/ezyang/htmlpurifier/tree/v4.16.0"
},
"time": "2022-09-18T07:06:19+00:00"
},
{ {
"name": "guzzlehttp/guzzle", "name": "guzzlehttp/guzzle",
"version": "7.7.0", "version": "7.7.0",
@ -728,17 +667,88 @@
"time": "2023-03-20T11:46:41+00:00" "time": "2023-03-20T11:46:41+00:00"
}, },
{ {
"name": "phpoffice/phpspreadsheet", "name": "openswoole/core",
"version": "1.29.0", "version": "22.1.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git", "url": "https://github.com/openswoole/core.git",
"reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0" "reference": "06dae68fdac73341ccf565ecef388434bd893141"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fde2ccf55eaef7e86021ff1acce26479160a0fa0", "url": "https://api.github.com/repos/openswoole/core/zipball/06dae68fdac73341ccf565ecef388434bd893141",
"reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0", "reference": "06dae68fdac73341ccf565ecef388434bd893141",
"shasum": ""
},
"require": {
"ext-openswoole": ">=22.0",
"php": ">=7.4",
"psr/http-message": "^1.0 || ^2.0",
"psr/http-server-middleware": "^1.0.0"
},
"require-dev": {
"ext-curl": "*",
"ext-sockets": "*",
"friendsofphp/php-cs-fixer": "^3.6",
"openswoole/ide-helper": "^22.0",
"php-http/psr7-integration-tests": "^1.1",
"phpunit/phpunit": "^9.5"
},
"suggest": {
"ext-mysqli": "*",
"ext-pdo": "*",
"ext-redis": "Required to use redis database, and the required version is greater than or equal to 3.1.3"
},
"type": "library",
"autoload": {
"files": [
"src/Coroutine/functions.php"
],
"psr-4": {
"OpenSwoole\\Core\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "OpenSwoole Group",
"email": "hello@openswoole.com"
}
],
"description": "Openswoole core library",
"homepage": "https://openswoole.com",
"keywords": [
"http",
"http2",
"mqtt",
"openswoole",
"php",
"tcp",
"websocket"
],
"support": {
"docs": "https://openswoole.com/docs",
"issues": "https://github.com/openswoole/openswoole/issues",
"pull-request": "https://github.com/openswoole/openswoole/pulls",
"source": "https://github.com/openswoole/openswoole"
},
"time": "2023-12-10T19:02:13+00:00"
},
{
"name": "phpoffice/phpspreadsheet",
"version": "2.1.0",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "dbed77bd3a0f68f96c0dd68ad4499d5674fecc3e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/dbed77bd3a0f68f96c0dd68ad4499d5674fecc3e",
"reference": "dbed77bd3a0f68f96c0dd68ad4499d5674fecc3e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -755,25 +765,24 @@
"ext-xmlwriter": "*", "ext-xmlwriter": "*",
"ext-zip": "*", "ext-zip": "*",
"ext-zlib": "*", "ext-zlib": "*",
"ezyang/htmlpurifier": "^4.15",
"maennchen/zipstream-php": "^2.1 || ^3.0", "maennchen/zipstream-php": "^2.1 || ^3.0",
"markbaker/complex": "^3.0", "markbaker/complex": "^3.0",
"markbaker/matrix": "^3.0", "markbaker/matrix": "^3.0",
"php": "^7.4 || ^8.0", "php": "^8.0",
"psr/http-client": "^1.0", "psr/http-client": "^1.0",
"psr/http-factory": "^1.0", "psr/http-factory": "^1.0",
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0" "psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
}, },
"require-dev": { "require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-main", "dealerdirect/phpcodesniffer-composer-installer": "dev-main",
"dompdf/dompdf": "^1.0 || ^2.0", "dompdf/dompdf": "^2.0",
"friendsofphp/php-cs-fixer": "^3.2", "friendsofphp/php-cs-fixer": "^3.2",
"mitoteam/jpgraph": "^10.3", "mitoteam/jpgraph": "^10.3",
"mpdf/mpdf": "^8.1.1", "mpdf/mpdf": "^8.1.1",
"phpcompatibility/php-compatibility": "^9.3", "phpcompatibility/php-compatibility": "^9.3",
"phpstan/phpstan": "^1.1", "phpstan/phpstan": "^1.1",
"phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^8.5 || ^9.0 || ^10.0", "phpunit/phpunit": "^9.6",
"squizlabs/php_codesniffer": "^3.7", "squizlabs/php_codesniffer": "^3.7",
"tecnickcom/tcpdf": "^6.5" "tecnickcom/tcpdf": "^6.5"
}, },
@ -828,9 +837,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.0" "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/2.1.0"
}, },
"time": "2023-06-14T22:48:31+00:00" "time": "2024-05-11T04:17:56+00:00"
}, },
{ {
"name": "psr/cache", "name": "psr/cache",
@ -1144,6 +1153,119 @@
}, },
"time": "2023-04-04T09:54:51+00:00" "time": "2023-04-04T09:54:51+00:00"
}, },
{
"name": "psr/http-server-handler",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-server-handler.git",
"reference": "84c4fb66179be4caaf8e97bd239203245302e7d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4",
"reference": "84c4fb66179be4caaf8e97bd239203245302e7d4",
"shasum": ""
},
"require": {
"php": ">=7.0",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Server\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP server-side request handler",
"keywords": [
"handler",
"http",
"http-interop",
"psr",
"psr-15",
"psr-7",
"request",
"response",
"server"
],
"support": {
"source": "https://github.com/php-fig/http-server-handler/tree/1.0.2"
},
"time": "2023-04-10T20:06:20+00:00"
},
{
"name": "psr/http-server-middleware",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-server-middleware.git",
"reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829",
"reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829",
"shasum": ""
},
"require": {
"php": ">=7.0",
"psr/http-message": "^1.0 || ^2.0",
"psr/http-server-handler": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Server\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP server-side middleware",
"keywords": [
"http",
"http-interop",
"middleware",
"psr",
"psr-15",
"psr-7",
"request",
"response"
],
"support": {
"issues": "https://github.com/php-fig/http-server-middleware/issues",
"source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2"
},
"time": "2023-04-11T06:14:47+00:00"
},
{ {
"name": "psr/log", "name": "psr/log",
"version": "3.0.0", "version": "3.0.0",
@ -1619,16 +1741,16 @@
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
"version": "v3.3.0", "version": "v3.5.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git", "url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1637,7 +1759,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "3.4-dev" "dev-main": "3.5-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/contracts", "name": "symfony/contracts",
@ -1666,7 +1788,7 @@
"description": "A generic function and convention to trigger deprecation notices", "description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0"
}, },
"funding": [ "funding": [
{ {
@ -1682,7 +1804,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-05-23T14:45:45+00:00" "time": "2024-04-18T09:32:20+00:00"
}, },
{ {
"name": "symfony/error-handler", "name": "symfony/error-handler",
@ -2377,25 +2499,25 @@
}, },
{ {
"name": "symfony/intl", "name": "symfony/intl",
"version": "v6.3.2", "version": "v7.0.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/intl.git", "url": "https://github.com/symfony/intl.git",
"reference": "1f8cb145c869ed089a8531c51a6a4b31ed0b3c69" "reference": "dd12042707110995e2e7d80103f8d9928bea8621"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/intl/zipball/1f8cb145c869ed089a8531c51a6a4b31ed0b3c69", "url": "https://api.github.com/repos/symfony/intl/zipball/dd12042707110995e2e7d80103f8d9928bea8621",
"reference": "1f8cb145c869ed089a8531c51a6a4b31ed0b3c69", "reference": "dd12042707110995e2e7d80103f8d9928bea8621",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=8.1" "php": ">=8.2"
}, },
"require-dev": { "require-dev": {
"symfony/filesystem": "^5.4|^6.0", "symfony/filesystem": "^6.4|^7.0",
"symfony/finder": "^5.4|^6.0", "symfony/finder": "^6.4|^7.0",
"symfony/var-exporter": "^5.4|^6.0" "symfony/var-exporter": "^6.4|^7.0"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@ -2403,7 +2525,8 @@
"Symfony\\Component\\Intl\\": "" "Symfony\\Component\\Intl\\": ""
}, },
"exclude-from-classmap": [ "exclude-from-classmap": [
"/Tests/" "/Tests/",
"/Resources/data/"
] ]
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
@ -2439,7 +2562,7 @@
"localization" "localization"
], ],
"support": { "support": {
"source": "https://github.com/symfony/intl/tree/v6.3.2" "source": "https://github.com/symfony/intl/tree/v7.0.7"
}, },
"funding": [ "funding": [
{ {
@ -2455,20 +2578,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-07-20T07:43:09+00:00" "time": "2024-04-18T09:29:19+00:00"
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.27.0", "version": "v1.29.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git", "url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a" "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a", "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2482,9 +2605,6 @@
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill" "url": "https://github.com/symfony/polyfill"
@ -2521,7 +2641,7 @@
"portable" "portable"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
}, },
"funding": [ "funding": [
{ {
@ -2537,20 +2657,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-11-03T14:55:06+00:00" "time": "2024-01-29T20:11:03+00:00"
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
"version": "v1.27.0", "version": "v1.29.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git", "url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2564,9 +2684,6 @@
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill" "url": "https://github.com/symfony/polyfill"
@ -2604,7 +2721,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
}, },
"funding": [ "funding": [
{ {
@ -2620,20 +2737,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-11-03T14:55:06+00:00" "time": "2024-01-29T20:11:03+00:00"
}, },
{ {
"name": "symfony/polyfill-php80", "name": "symfony/polyfill-php80",
"version": "v1.28.0", "version": "v1.29.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php80.git", "url": "https://github.com/symfony/polyfill-php80.git",
"reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
"reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2641,9 +2758,6 @@
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill" "url": "https://github.com/symfony/polyfill"
@ -2687,7 +2801,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0"
}, },
"funding": [ "funding": [
{ {
@ -2703,7 +2817,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-01-26T09:26:14+00:00" "time": "2024-01-29T20:11:03+00:00"
}, },
{ {
"name": "symfony/polyfill-php83", "name": "symfony/polyfill-php83",
@ -3516,25 +3630,25 @@
}, },
{ {
"name": "twig/intl-extra", "name": "twig/intl-extra",
"version": "v3.7.1", "version": "v3.10.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/twigphp/intl-extra.git", "url": "https://github.com/twigphp/intl-extra.git",
"reference": "4f4fe572f635534649cc069e1dafe4a8ad63774d" "reference": "693f6beb8ca91fc6323e01b3addf983812f65c93"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/twigphp/intl-extra/zipball/4f4fe572f635534649cc069e1dafe4a8ad63774d", "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/693f6beb8ca91fc6323e01b3addf983812f65c93",
"reference": "4f4fe572f635534649cc069e1dafe4a8ad63774d", "reference": "693f6beb8ca91fc6323e01b3addf983812f65c93",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.1.3", "php": ">=7.2.5",
"symfony/intl": "^5.4|^6.0", "symfony/intl": "^5.4|^6.4|^7.0",
"twig/twig": "^2.7|^3.0" "twig/twig": "^3.10"
}, },
"require-dev": { "require-dev": {
"symfony/phpunit-bridge": "^5.4|^6.3" "symfony/phpunit-bridge": "^6.4|^7.0"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@ -3564,7 +3678,7 @@
"twig" "twig"
], ],
"support": { "support": {
"source": "https://github.com/twigphp/intl-extra/tree/v3.7.1" "source": "https://github.com/twigphp/intl-extra/tree/v3.10.0"
}, },
"funding": [ "funding": [
{ {
@ -3576,33 +3690,41 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-07-29T15:34:56+00:00" "time": "2024-05-11T07:35:57+00:00"
}, },
{ {
"name": "twig/twig", "name": "twig/twig",
"version": "v3.6.1", "version": "v3.10.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/twigphp/Twig.git", "url": "https://github.com/twigphp/Twig.git",
"reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd" "reference": "67f29781ffafa520b0bbfbd8384674b42db04572"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd", "url": "https://api.github.com/repos/twigphp/Twig/zipball/67f29781ffafa520b0bbfbd8384674b42db04572",
"reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd", "reference": "67f29781ffafa520b0bbfbd8384674b42db04572",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.2.5", "php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-ctype": "^1.8", "symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3" "symfony/polyfill-mbstring": "^1.3",
"symfony/polyfill-php80": "^1.22"
}, },
"require-dev": { "require-dev": {
"psr/container": "^1.0|^2.0", "psr/container": "^1.0|^2.0",
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
"files": [
"src/Resources/core.php",
"src/Resources/debug.php",
"src/Resources/escaper.php",
"src/Resources/string_loader.php"
],
"psr-4": { "psr-4": {
"Twig\\": "src/" "Twig\\": "src/"
} }
@ -3635,7 +3757,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/twigphp/Twig/issues", "issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.6.1" "source": "https://github.com/twigphp/Twig/tree/v3.10.3"
}, },
"funding": [ "funding": [
{ {
@ -3647,7 +3769,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-06-08T12:52:13+00:00" "time": "2024-05-16T10:04:27+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [
@ -5393,8 +5515,8 @@
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
"php": "~8.2", "php": "~8.3",
"ext-sodium": "~8.2.4" "ext-sodium": "~8.3"
}, },
"platform-dev": [], "platform-dev": [],
"plugin-api-version": "2.2.0" "plugin-api-version": "2.2.0"

17
ebala-socket.service Executable file
View File

@ -0,0 +1,17 @@
[Unit]
Description=Ebala-socket
Wants=network.target
After=syslog.target network-online.target
[Service]
ExecStart=sudo -u www-data /usr/bin/php /var/www/ebala/mirzaev/ebala/system/public/socket.php
PIDFile=/var/run/php/ebala-socket.pid
RemainAfterExit=no
RuntimeMaxSec=3600s
Restart=always
RestartSec=30s
[Install]
WantedBy=multi-user.target

View File

@ -106,123 +106,158 @@ final class account extends core
*/ */
public function update(array $parameters = []): ?string public function update(array $parameters = []): ?string
{ {
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) { try {
// Авторизован аккаунт администратора или оператора if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
// Авторизован аккаунт администратора или оператора
// Инициализация данных аккаунта // Инициализация данных аккаунта
$account = model::read('d._key == "' . $parameters['id'] . '"'); $account = model::read('d._key == "' . $parameters['id'] . '"');
if (!empty($account)) { if (!empty($account)) {
// Найден аккаунт // Найден аккаунт
// Инициализация буфера изменённости пароля // Инициализация буфера изменённости пароля
$password = false; $password = false;
// Инициализация параметров (перезапись переданными значениями) // Инициализация параметров (перезапись переданными значениями)
if ($parameters['name_first'] !== $account->name['first']) $account->name = ['first' => $parameters['name_first']] + $account->name; if ($parameters['name_first'] !== $account->name['first']) $account->name = ['first' => $parameters['name_first']] + $account->name;
if ($parameters['name_second'] !== $account->name['second']) $account->name = ['second' => $parameters['name_second']] + $account->name; if ($parameters['name_second'] !== $account->name['second']) $account->name = ['second' => $parameters['name_second']] + $account->name;
if ($parameters['name_last'] !== $account->name['last']) $account->name = ['last' => $parameters['name_last']] + $account->name; if ($parameters['name_last'] !== $account->name['last']) $account->name = ['last' => $parameters['name_last']] + $account->name;
if ($parameters['number'] !== $account->number) if ($parameters['number'] !== $account->number)
if (mb_strlen($parameters['number']) === 11) $account->number = $parameters['number']; if (mb_strlen($parameters['number']) === 11) $account->number = $parameters['number'];
else throw new exception('Номер должен состоять из 11 символов'); else throw new exception('Номер должен состоять из 11 символов');
if ($parameters['mail'] !== $account->mail) $account->mail = $parameters['mail']; if ($parameters['mail'] !== $account->mail) $account->mail = $parameters['mail'];
if (!empty($parameters['password']) && !@sodium_crypto_pwhash_str_verify($parameters['password'], $account->password ?? '') && $password = true) if (!empty($parameters['password']) && !@sodium_crypto_pwhash_str_verify($parameters['password'], $account->password ?? '') && $password = true)
if (mb_strlen($parameters['password']) > 6) $account->password = sodium_crypto_pwhash_str( if (mb_strlen($parameters['password']) > 6) $account->password = sodium_crypto_pwhash_str(
$parameters['password'], $parameters['password'],
SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE, SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE,
SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE
); );
else throw new exception('Пароль должен быть длиннее 6 символов'); else throw new exception('Пароль должен быть длиннее 6 символов');
if ($parameters['commentary'] !== $account->commentary) $account->commentary = $parameters['commentary']; if ($parameters['commentary'] !== $account->commentary) $account->commentary = $parameters['commentary'];
if ($parameters['transactions'] !== $account->transactions) if (!empty($parameters['transactions']) && $parameters['transactions'] !== $account->transactions)
$account->transactions = match ($parameters['transactions']) { $account->transactions = match ($parameters['transactions']) {
'true' => true, 'true' => true,
'false' => false, 'false' => false,
default => false default => false
}; };
if (_core::update($account)) { if (_core::update($account)) {
// Записаны данные аккаунта // Записаны данные аккаунта
if ($account->type === 'worker') { if ($account->type === 'worker') {
// Сотрудник // Сотрудник
// Инициализация строки в глобальную переменную шаблонизатора // Инициализация строки в глобальную переменную шаблонизатора
$this->view->rows = registry::workers( $this->view->rows = registry::workers(
before: sprintf( before: sprintf(
"FILTER a._id == '%s' && a.deleted != true", "FILTER a._id == '%s' && a.deleted != true",
$account->getId() $account->getId()
), ),
after: <<<AQL after: <<<AQL
let account = (IS_SAME_COLLECTION('account', a) ? a : b) let account = (IS_SAME_COLLECTION('account', a) ? a : b)
let worker = (IS_SAME_COLLECTION('worker', a) ? a : b) let worker = (IS_SAME_COLLECTION('worker', a) ? a : b)
FILTER account.type == 'worker' && b.deleted != true FILTER account.type == 'worker' && b.deleted != true
AQL, AQL,
amount: 1 amount: 1
); );
} else if ($account->type === 'market') { } else if ($account->type === 'market') {
// Магазин // Магазин
// Инициализация строки в глобальную переменную шаблонизатора // Инициализация строки в глобальную переменную шаблонизатора
$this->view->rows = registry::markets( $this->view->rows = registry::markets(
before: sprintf( before: sprintf(
"FILTER a._id == '%s' && a.deleted != true", "FILTER a._id == '%s' && a.deleted != true",
$account->getId() $account->getId()
), ),
after: <<<AQL after: <<<AQL
let account = (IS_SAME_COLLECTION('account', a) ? a : b) let account = (IS_SAME_COLLECTION('account', a) ? a : b)
let market = (IS_SAME_COLLECTION('market', a) ? a : b) let market = (IS_SAME_COLLECTION('market', a) ? a : b)
FILTER account.type == 'market' && b.deleted != true FILTER account.type == 'market' && b.deleted != true
AQL, AQL,
amount: 1 amount: 1
); );
} else { } else {
// Администратор или оператор (подразумевается) // Администратор или оператор (подразумевается)
// Инициализация строки в глобальную переменную шаблонизатора // Инициализация строки в глобальную переменную шаблонизатора
$this->view->rows = [['account' => $account->getAll()]]; $this->view->rows = [['account' => $account->getAll()]];
} }
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение) // Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
$this->view->page = null; $this->view->page = null;
// Запись заголовков ответа // Запись заголовков ответа
header('Content-Type: application/json'); header('Content-Type: application/json');
header('Content-Encoding: none'); header('Content-Encoding: none');
header('X-Accel-Buffering: no'); header('X-Accel-Buffering: no');
// Инициализация буфера вывода // Инициализация буфера вывода
ob_start(); ob_start();
// Инициализация буфера ответа // Инициализация буфера ответа
$return = [ $return = [
'updated' => true, 'updated' => true,
'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . $account->type . 's.html'), 'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . $account->type . 's.html'),
'errors' => self::parse_only_text($this->errors) 'errors' => self::parse_only_text($this->errors)
]; ];
if ($password) $return['clipboard'] = match ($account->type) { if ($password) $return['clipboard'] = match ($account->type) {
'worker' => 'Номер: ' . model::worker($account->getId())?->number, 'worker' => 'Номер: ' . model::worker($account->getId())?->number,
'market' => 'Идентификатор: ' . model::market($account->getId())?->id, 'market' => 'Идентификатор: ' . model::market($account->getId())?->id,
'operator' => "Идентификатор: {$account->getKey()}", 'operator' => "Идентификатор: {$account->getKey()}",
'administrator' => "Идентификатор: {$account->getKey()}", 'administrator' => "Идентификатор: {$account->getKey()}",
default => "Идентификатор: {$account->getKey()}" default => "Идентификатор: {$account->getKey()}"
} }
. "\nПароль: {$parameters['password']}"; . "\nПароль: {$parameters['password']}";
// Генерация ответа // Генерация ответа
echo json_encode($return); echo json_encode($return);
// Запись заголовков ответа // Запись заголовков ответа
header('Content-Length: ' . ob_get_length()); header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода // Отправка и деинициализация буфера вывода
ob_end_flush(); ob_end_flush();
flush(); flush();
} else throw new exception('Не удалось записать изменения в базу данных'); } else throw new exception('Не удалось записать изменения в базу данных');
} else throw new exception('Не удалось найти аккаунт'); } else throw new exception('Не удалось найти аккаунт');
}
} catch (exception $e) {
// Write to the errors registry
$this->errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Инициализация буфера ответа
$return = [
'updated' => false,
'errors' => self::parse_only_text($this->errors)
];
// Генерация ответа
echo json_encode($return);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
} }
// Возврат (провал) // Возврат (провал)
return null; return null;
} }
@ -234,75 +269,110 @@ final class account extends core
*/ */
public function delete(array $parameters = []): ?string public function delete(array $parameters = []): ?string
{ {
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) { try {
// Авторизован аккаунт администратора или оператора if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
// Авторизован аккаунт администратора или оператора
// Инициализация данных аккаунта // Инициализация данных аккаунта
$account = model::read('d._key == "' . $parameters['id'] . '"'); $account = model::read('d._key == "' . $parameters['id'] . '"');
if (!empty($account)) { if (!empty($account)) {
// Найден аккаунт // Найден аккаунт
// Удаление
$account->active = false;
$account->deleted = true;
if ($account->type === 'worker') {
// Сотрудник
// Инициализация сотрудника
if (empty($worker = model::worker($account->getId()))) throw new exception('Не удалось инициализировать сотрудника');
// Удаление // Удаление
$worker->active = false; $account->active = false;
$worker->deleted = true; $account->deleted = true;
// Запись в ArangoDB if ($account->type === 'worker') {
if (!_core::update($worker)) throw throw new exception('Не удалось записать изменения в базу данных'); // Сотрудник
} else if ($account->type === 'market') {
// Магазин
// Инициализация магазина // Инициализация сотрудника
if (empty($market = model::market($account->getId()))) throw new exception('Не удалось инициализировать магазин'); if (empty($worker = model::worker($account->getId()))) throw new exception('Не удалось инициализировать сотрудника');
// Удаление // Удаление
$market->active = false; $worker->active = false;
$market->deleted = true; $worker->deleted = true;
// Запись в ArangoDB // Запись в ArangoDB
if (!_core::update($market)) throw throw new exception('Не удалось записать изменения в базу данных'); if (!_core::update($worker)) throw throw new exception('Не удалось записать изменения в базу данных');
} } else if ($account->type === 'market') {
// Магазин
if (_core::update($account)) { // Инициализация магазина
// Записаны данные аккаунта if (empty($market = model::market($account->getId()))) throw new exception('Не удалось инициализировать магазин');
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение) // Удаление
$this->view->page = null; $market->active = false;
$market->deleted = true;
// Запись заголовков ответа // Запись в ArangoDB
header('Content-Type: application/json'); if (!_core::update($market)) throw throw new exception('Не удалось записать изменения в базу данных');
header('Content-Encoding: none'); }
header('X-Accel-Buffering: no');
// Инициализация буфера вывода if (_core::update($account)) {
ob_start(); // Записаны данные аккаунта
// Генерация ответа // Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
echo json_encode([ $this->view->page = null;
'deleted' => true,
'errors' => self::parse_only_text($this->errors)
]);
// Запись заголовков ответа // Запись заголовков ответа
header('Content-Length: ' . ob_get_length()); header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Отправка и деинициализация буфера вывода // Инициализация буфера вывода
ob_end_flush(); ob_start();
flush();
} else throw new exception('Не удалось записать изменения в базу данных'); // Генерация ответа
} else throw new exception('Не удалось найти аккаунт'); echo json_encode([
'deleted' => true,
'errors' => self::parse_only_text($this->errors)
]);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
} else throw new exception('Не удалось записать изменения в базу данных');
} else throw new exception('Не удалось найти аккаунт');
}
} catch (exception $e) {
// Write to the errors registry
$this->errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Инициализация буфера ответа
$return = [
'deleted' => false,
'errors' => self::parse_only_text($this->errors)
];
// Генерация ответа
echo json_encode($return);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
} }
// Возврат (провал) // Возврат (провал)
return null; return null;
} }
@ -314,46 +384,81 @@ final class account extends core
*/ */
public function ban(array $parameters = []): ?string public function ban(array $parameters = []): ?string
{ {
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) { try {
// Авторизован аккаунт администратора или оператора if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
// Авторизован аккаунт администратора или оператора
// Инициализация данных аккаунта // Инициализация данных аккаунта
$account = model::read('d._key == "' . $parameters['id'] . '"'); $account = model::read('d._key == "' . $parameters['id'] . '"');
if (!empty($account)) { if (!empty($account)) {
// Найден аккаунт // Найден аккаунт
// Блокирование // Блокирование
$account->active = false; $account->active = false;
$account->banned = true; $account->banned = true;
if (_core::update($account)) { if (_core::update($account)) {
// Записаны данные аккаунта // Записаны данные аккаунта
// Запись заголовков ответа // Запись заголовков ответа
header('Content-Type: application/json'); header('Content-Type: application/json');
header('Content-Encoding: none'); header('Content-Encoding: none');
header('X-Accel-Buffering: no'); header('X-Accel-Buffering: no');
// Инициализация буфера вывода // Инициализация буфера вывода
ob_start(); ob_start();
// Генерация ответа // Генерация ответа
echo json_encode([ echo json_encode([
'banned' => true, 'banned' => true,
'errors' => self::parse_only_text($this->errors) 'errors' => self::parse_only_text($this->errors)
]); ]);
// Запись заголовков ответа // Запись заголовков ответа
header('Content-Length: ' . ob_get_length()); header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода // Отправка и деинициализация буфера вывода
ob_end_flush(); ob_end_flush();
flush(); flush();
} else throw new exception('Не удалось записать изменения в базу данных'); } else throw new exception('Не удалось записать изменения в базу данных');
} else throw new exception('Не удалось найти аккаунт'); } else throw new exception('Не удалось найти аккаунт');
}
} catch (exception $e) {
// Write to the errors registry
$this->errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Инициализация буфера ответа
$return = [
'banned' => false,
'errors' => self::parse_only_text($this->errors)
];
// Генерация ответа
echo json_encode($return);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
} }
// Возврат (провал) // Возврат (провал)
return null; return null;
} }
@ -365,46 +470,81 @@ final class account extends core
*/ */
public function unban(array $parameters = []): ?string public function unban(array $parameters = []): ?string
{ {
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) { try {
// Авторизован аккаунт администратора или оператора if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
// Авторизован аккаунт администратора или оператора
// Инициализация данных аккаунта // Инициализация данных аккаунта
$account = model::read('d._key == "' . $parameters['id'] . '"'); $account = model::read('d._key == "' . $parameters['id'] . '"');
if (!empty($account)) { if (!empty($account)) {
// Найден аккаунт // Найден аккаунт
// Блокирование // Блокирование
$account->active = true; $account->active = true;
$account->banned = false; $account->banned = false;
if (_core::update($account)) { if (_core::update($account)) {
// Записаны данные аккаунта // Записаны данные аккаунта
// Запись заголовков ответа // Запись заголовков ответа
header('Content-Type: application/json'); header('Content-Type: application/json');
header('Content-Encoding: none'); header('Content-Encoding: none');
header('X-Accel-Buffering: no'); header('X-Accel-Buffering: no');
// Инициализация буфера вывода // Инициализация буфера вывода
ob_start(); ob_start();
// Генерация ответа // Генерация ответа
echo json_encode([ echo json_encode([
'unbanned' => true, 'unbanned' => true,
'errors' => self::parse_only_text($this->errors) 'errors' => self::parse_only_text($this->errors)
]); ]);
// Запись заголовков ответа // Запись заголовков ответа
header('Content-Length: ' . ob_get_length()); header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода // Отправка и деинициализация буфера вывода
ob_end_flush(); ob_end_flush();
flush(); flush();
} else throw new exception('Не удалось записать изменения в базу данных'); } else throw new exception('Не удалось записать изменения в базу данных');
} else throw new exception('Не удалось найти аккаунт'); } else throw new exception('Не удалось найти аккаунт');
}
} catch (exception $e) {
// Write to the errors registry
$this->errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Инициализация буфера ответа
$return = [
'unbanned' => false,
'errors' => self::parse_only_text($this->errors)
];
// Генерация ответа
echo json_encode($return);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
} }
// Возврат (провал) // Возврат (провал)
return null; return null;
} }

View File

@ -234,12 +234,35 @@ final class administrator extends core
if (empty($account)) throw new exception('Не удалось создать аккаунт'); if (empty($account)) throw new exception('Не удалось создать аккаунт');
} catch (exception $e) { } catch (exception $e) {
// Write to the errors registry // Write to the errors registry
$this->errors['account'][] = [ $this->errors['administrator'][] = [
'text' => $e->getMessage(), 'text' => $e->getMessage(),
'file' => $e->getFile(), 'file' => $e->getFile(),
'line' => $e->getLine(), 'line' => $e->getLine(),
'stack' => $e->getTrace() 'stack' => $e->getTrace()
]; ];
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Инициализация буфера ответа
$return = [
'errors' => self::parse_only_text($this->errors)
];
// Генерация ответа
echo json_encode($return);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
} }
// Инициализация идентификатора аккаунта (ключ документа инстанции аккаунта в базе данных) // Инициализация идентификатора аккаунта (ключ документа инстанции аккаунта в базе данных)

View File

@ -56,7 +56,7 @@ class core extends controller
public function __construct(bool $initialize = true) public function __construct(bool $initialize = true)
{ {
// Блокировка запросов от CloudFlare // Блокировка запросов от CloudFlare
if ($_SERVER['HTTP_USER_AGENT'] === 'nginx-ssl early hints') return; if (!empty($_SERVER['HTTP_USER_AGENT']) && $_SERVER['HTTP_USER_AGENT'] === 'nginx-ssl early hints') return;
parent::__construct($initialize); parent::__construct($initialize);

View File

@ -341,7 +341,7 @@ final class market extends core
throw new exception('Не инициализирован аккаунт'); throw new exception('Не инициализирован аккаунт');
} catch (exception $e) { } catch (exception $e) {
// Write to the errors registry // Write to the errors registry
$this->errors['account'][] = [ $this->errors['market'][] = [
'text' => $e->getMessage(), 'text' => $e->getMessage(),
'file' => $e->getFile(), 'file' => $e->getFile(),
'line' => $e->getLine(), 'line' => $e->getLine(),
@ -398,49 +398,83 @@ final class market extends core
*/ */
public function ban(array $parameters = []): ?string public function ban(array $parameters = []): ?string
{ {
if ($this->account->status() && $this->account->type === 'market') { try {
// Авторизован аккаунт магазина if ($this->account->status() && $this->account->type === 'market') {
// Авторизован аккаунт магазина
// Инициализация данных магазина // Инициализация данных магазина
$market = account::market($this->account->getId()); $market = account::market($this->account->getId());
if ($market instanceof _document) { if ($market instanceof _document) {
// Найден магазин // Найден магазин
// Блокировка сотрудника // Блокировка сотрудника
if (!in_array($parameters['worker'], $market->bans ??= [], true)) $market->bans = $market->bans + [$parameters['worker']]; if (!in_array($parameters['worker'], $market->bans ??= [], true)) $market->bans = $market->bans + [$parameters['worker']];
if (_core::update($market)) { if (_core::update($market)) {
// Записаны данные магазина // Записаны данные магазина
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение) // Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
$this->view->page = null; $this->view->page = null;
// Запись заголовков ответа // Запись заголовков ответа
header('Content-Type: application/json'); header('Content-Type: application/json');
header('Content-Encoding: none'); header('Content-Encoding: none');
header('X-Accel-Buffering: no'); header('X-Accel-Buffering: no');
// Инициализация буфера вывода // Инициализация буфера вывода
ob_start(); ob_start();
// Инициализация буфера ответа // Инициализация буфера ответа
$return = [ $return = [
'banned' => true, 'banned' => true,
'errors' => self::parse_only_text($this->errors) 'errors' => self::parse_only_text($this->errors)
]; ];
// Генерация ответа // Генерация ответа
echo json_encode($return); echo json_encode($return);
// Запись заголовков ответа // Запись заголовков ответа
header('Content-Length: ' . ob_get_length()); header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода // Отправка и деинициализация буфера вывода
ob_end_flush(); ob_end_flush();
flush(); flush();
} else throw new exception('Не удалось записать изменения в базу данных'); } else throw new exception('Не удалось записать изменения в базу данных');
} else throw new exception('Не удалось найти магазин'); } else throw new exception('Не удалось найти магазин');
}
} catch (exception $e) {
// Write to the errors registry
$this->errors['market'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Инициализация буфера ответа
$return = [
'banned' => false,
'errors' => self::parse_only_text($this->errors)
];
// Генерация ответа
echo json_encode($return);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
} }
// Возврат (провал) // Возврат (провал)
@ -454,49 +488,83 @@ final class market extends core
*/ */
public function unban(array $parameters = []): ?string public function unban(array $parameters = []): ?string
{ {
if ($this->account->status() && $this->account->type === 'market') { try {
// Авторизован аккаунт магазина if ($this->account->status() && $this->account->type === 'market') {
// Авторизован аккаунт магазина
// Инициализация данных магазина // Инициализация данных магазина
$market = account::market($this->account->getId()); $market = account::market($this->account->getId());
if ($market instanceof _document) { if ($market instanceof _document) {
// Найден магазин // Найден магазин
// Разблокировка сотрудника // Разблокировка сотрудника
if (in_array($parameters['worker'], $market->bans ??= [], true)) $market->bans = array_diff($market->bans ??= [], [$parameters['worker']]); if (in_array($parameters['worker'], $market->bans ??= [], true)) $market->bans = array_diff($market->bans ??= [], [$parameters['worker']]);
if (_core::update($market)) { if (_core::update($market)) {
// Записаны данные магазина // Записаны данные магазина
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение) // Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
$this->view->page = null; $this->view->page = null;
// Запись заголовков ответа // Запись заголовков ответа
header('Content-Type: application/json'); header('Content-Type: application/json');
header('Content-Encoding: none'); header('Content-Encoding: none');
header('X-Accel-Buffering: no'); header('X-Accel-Buffering: no');
// Инициализация буфера вывода // Инициализация буфера вывода
ob_start(); ob_start();
// Инициализация буфера ответа // Инициализация буфера ответа
$return = [ $return = [
'unbanned' => true, 'unbanned' => true,
'errors' => self::parse_only_text($this->errors) 'errors' => self::parse_only_text($this->errors)
]; ];
// Генерация ответа // Генерация ответа
echo json_encode($return); echo json_encode($return);
// Запись заголовков ответа // Запись заголовков ответа
header('Content-Length: ' . ob_get_length()); header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода // Отправка и деинициализация буфера вывода
ob_end_flush(); ob_end_flush();
flush(); flush();
} else throw new exception('Не удалось записать изменения в базу данных'); } else throw new exception('Не удалось записать изменения в базу данных');
} else throw new exception('Не удалось найти магазин'); } else throw new exception('Не удалось найти магазин');
}
} catch (exception $e) {
// Write to the errors registry
$this->errors['market'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Инициализация буфера ответа
$return = [
'unbanned' => false,
'errors' => self::parse_only_text($this->errors)
];
// Генерация ответа
echo json_encode($return);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
} }
// Возврат (провал) // Возврат (провал)
@ -510,94 +578,15 @@ final class market extends core
*/ */
public function banned(array $parameters = []): ?string public function banned(array $parameters = []): ?string
{ {
if ($this->account->status() && $this->account->type === 'market') { try {
// Авторизован аккаунт магазина if ($this->account->status() && $this->account->type === 'market') {
// Авторизован аккаунт магазина
// Инициализация данных магазина // Инициализация данных магазина
$market = account::market($this->account->getId()); $market = account::market($this->account->getId());
if ($market instanceof _document) { if ($market instanceof _document) {
// Найден магазин // Найден магазин
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
$this->view->page = null;
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Инициализация буфера ответа
$return = [
'banned' => in_array($parameters['worker'], $market->bans ?? [], true),
'errors' => self::parse_only_text($this->errors)
];
// Генерация ответа
echo json_encode($return);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
} else throw new exception('Не удалось найти магазин');
}
return null;
}
/**
* Обновить данные
*
* @param array $parameters Параметры запроса
*/
public function update(array $parameters = []): ?string
{
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
// Авторизован аккаунт администратора или оператора
// Инициализация данных магазина
$market = model::read('d.id == "' . urldecode($parameters['id']) . '"');
if (!empty($market)) {
// Найден магазин
// Инициализация параметров (перезапись переданными значениями)
if ($parameters['name_first'] !== $market->name['first']) $market->name = ['first' => $parameters['name_first']] + $market->name;
if ($parameters['name_second'] !== $market->name['second']) $market->name = ['second' => $parameters['name_second']] + $market->name;
if ($parameters['name_last'] !== $market->name['last']) $market->name = ['last' => $parameters['name_last']] + $market->name;
if ($parameters['number'] !== $market->number)
if (mb_strlen($parameters['number']) === 11) $market->number = $parameters['number'];
else throw new exception('Номер должен состоять из 11 символов');
if ($parameters['mail'] !== $market->mail) $market->mail = $parameters['mail'];
if ($parameters['type'] !== $market->type) $market->type = $parameters['type'];
if ($parameters['city'] !== $market->city) $market->city = $parameters['city'];
if ($parameters['district'] !== $market->district) $market->district = $parameters['district'];
if ($parameters['address'] !== $market->address) $market->address = $parameters['address'];
if (!in_array($parameters['ban'], $market->bans, true)) $market->bans[] = $parameters['ban'];
if (_core::update($market)) {
// Записаны данные магазина
// Инициализация строки в глобальную переменную шаблонизатора
$this->view->rows = registry::markets(
before: sprintf(
"FILTER a._id == '%s' && a.deleted != true",
$market->getId()
),
after: <<<AQL
let account = (IS_SAME_COLLECTION('account', a) ? a : b)
let market = (IS_SAME_COLLECTION('market', a) ? a : b)
FILTER account.type == 'market' && b.deleted != true
AQL,
amount: 1,
target: model::COLLECTION
);
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение) // Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
$this->view->page = null; $this->view->page = null;
@ -612,8 +601,7 @@ final class market extends core
// Инициализация буфера ответа // Инициализация буфера ответа
$return = [ $return = [
'updated' => true, 'banned' => in_array($parameters['worker'], $market->bans ?? [], true),
'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'markets.html'),
'errors' => self::parse_only_text($this->errors) 'errors' => self::parse_only_text($this->errors)
]; ];
@ -626,8 +614,155 @@ final class market extends core
// Отправка и деинициализация буфера вывода // Отправка и деинициализация буфера вывода
ob_end_flush(); ob_end_flush();
flush(); flush();
} else throw new exception('Не удалось записать изменения в базу данных'); } else throw new exception('Не удалось найти магазин');
} else throw new exception('Не удалось найти аккаунт'); }
} catch (exception $e) {
// Write to the errors registry
$this->errors['market'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Инициализация буфера ответа
$return = [
'errors' => self::parse_only_text($this->errors)
];
// Генерация ответа
echo json_encode($return);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
}
return null;
}
/**
* Обновить данные
*
* @param array $parameters Параметры запроса
*/
public function update(array $parameters = []): ?string
{
try {
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
// Авторизован аккаунт администратора или оператора
// Инициализация данных магазина
$market = model::read('d.id == "' . urldecode($parameters['id']) . '"');
if (!empty($market)) {
// Найден магазин
// Инициализация параметров (перезапись переданными значениями)
if ($parameters['name_first'] !== $market->name['first']) $market->name = ['first' => $parameters['name_first']] + $market->name;
if ($parameters['name_second'] !== $market->name['second']) $market->name = ['second' => $parameters['name_second']] + $market->name;
if ($parameters['name_last'] !== $market->name['last']) $market->name = ['last' => $parameters['name_last']] + $market->name;
if ($parameters['number'] !== $market->number)
if (mb_strlen($parameters['number']) === 11) $market->number = $parameters['number'];
else throw new exception('Номер должен состоять из 11 символов');
if ($parameters['mail'] !== $market->mail) $market->mail = $parameters['mail'];
if ($parameters['type'] !== $market->type) $market->type = $parameters['type'];
if ($parameters['city'] !== $market->city) $market->city = $parameters['city'];
if ($parameters['district'] !== $market->district) $market->district = $parameters['district'];
if ($parameters['address'] !== $market->address) $market->address = $parameters['address'];
if (!in_array($parameters['ban'], $market->bans, true)) $market->bans[] = $parameters['ban'];
if (_core::update($market)) {
// Записаны данные магазина
// Инициализация строки в глобальную переменную шаблонизатора
$this->view->rows = registry::markets(
before: sprintf(
"FILTER a._id == '%s' && a.deleted != true",
$market->getId()
),
after: <<<AQL
let account = (IS_SAME_COLLECTION('account', a) ? a : b)
let market = (IS_SAME_COLLECTION('market', a) ? a : b)
FILTER account.type == 'market' && b.deleted != true
AQL,
amount: 1,
target: model::COLLECTION
);
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
$this->view->page = null;
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Инициализация буфера ответа
$return = [
'updated' => true,
'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'markets.html'),
'errors' => self::parse_only_text($this->errors)
];
// Генерация ответа
echo json_encode($return);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
} else throw new exception('Не удалось записать изменения в базу данных');
} else throw new exception('Не удалось найти аккаунт');
}
} catch (exception $e) {
// Write to the errors registry
$this->errors['market'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Инициализация буфера ответа
$return = [
'updated' => false,
'errors' => self::parse_only_text($this->errors)
];
// Генерация ответа
echo json_encode($return);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
} }
// Возврат (провал) // Возврат (провал)

View File

@ -233,12 +233,35 @@ final class operator extends core
if (empty($account)) throw new exception('Не удалось создать аккаунт'); if (empty($account)) throw new exception('Не удалось создать аккаунт');
} catch (exception $e) { } catch (exception $e) {
// Write to the errors registry // Write to the errors registry
$this->errors['account'][] = [ $this->errors['operator'][] = [
'text' => $e->getMessage(), 'text' => $e->getMessage(),
'file' => $e->getFile(), 'file' => $e->getFile(),
'line' => $e->getLine(), 'line' => $e->getLine(),
'stack' => $e->getTrace() 'stack' => $e->getTrace()
]; ];
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Инициализация буфера ответа
$return = [
'errors' => self::parse_only_text($this->errors)
];
// Генерация ответа
echo json_encode($return);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
} }
// Инициализация идентификатора аккаунта (ключ документа инстанции аккаунта в базе данных) // Инициализация идентификатора аккаунта (ключ документа инстанции аккаунта в базе данных)

View File

@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace mirzaev\ebala\controllers;
// Файлы проекта
use mirzaev\ebala\controllers\core,
mirzaev\ebala\controllers\traits\errors,
mirzaev\ebala\models\socket as model,
mirzaev\ebala\models\task,
mirzaev\ebala\models\session,
mirzaev\ebala\models\registry,
mirzaev\ebala\models\core as _core;
// Фреймворк ArangoDB
use mirzaev\arangodb\document;
// Библиотека для ArangoDB
use ArangoDBClient\Document as _document;
// Встроенные библиотеки
use exception;
/**
* Контроллер сокета
*
* @package mirzaev\ebala\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class socket extends core
{
use errors;
/**
* Регистрация
*
* @param array $parameters Параметры запроса
*/
public function registration(array $parameters = []): ?string
{
try {
if (model::session($this->session->getId(), $parameters['key'], $this->errors)) {
// Инициализировано соединение
// @todo сделать например возврат того что соединение удалось и на клиенте что-то оптимизировать (или не удалось чтобы повторил)
return null;
} else throw new exception('Не удалось зарегистрировать сокет');
} catch (exception $e) {
// Write to the errors registry
$this->errors['socket'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
// Возврат (провал)
return null;
}
}

View File

@ -274,6 +274,7 @@ final class task extends core
), ),
after: 'COLLECT x = worker, y = market, z = task OPTIONS { method: "sorted" }', after: 'COLLECT x = worker, y = market, z = task OPTIONS { method: "sorted" }',
page: (int) $this->session->buffer['worker']['tasks']['page'], page: (int) $this->session->buffer['worker']['tasks']['page'],
sort: 'z.date DESC, z.created DESC, z._key DESC, x.created DESC, y.created DESC, x._key DESC, y._key DESC',
target: empty($search) ? model::COLLECTION : 'registry_tasks', target: empty($search) ? model::COLLECTION : 'registry_tasks',
return: '{task: z, worker: x, market: y}', return: '{task: z, worker: x, market: y}',
binds: ['worker' => account::worker($this->account->getId())?->id] + (empty($search) ? [] : ['search' => $search]) binds: ['worker' => account::worker($this->account->getId())?->id] + (empty($search) ? [] : ['search' => $search])
@ -292,6 +293,7 @@ final class task extends core
), ),
after: 'COLLECT x = worker, y = market, z = task OPTIONS { method: "sorted" }', after: 'COLLECT x = worker, y = market, z = task OPTIONS { method: "sorted" }',
page: (int) $this->session->buffer['market']['tasks']['page'], page: (int) $this->session->buffer['market']['tasks']['page'],
sort: 'z.date DESC, z.created DESC, z._key DESC, x.created DESC, y.created DESC, x._key DESC, y._key DESC',
target: empty($search) ? model::COLLECTION : 'registry_tasks', target: empty($search) ? model::COLLECTION : 'registry_tasks',
return: '{task: z, worker: x, market: y}', return: '{task: z, worker: x, market: y}',
binds: ['market' => account::market($this->account->getId())?->id] + (empty($search) ? [] : ['search' => $search]) binds: ['market' => account::market($this->account->getId())?->id] + (empty($search) ? [] : ['search' => $search])
@ -310,6 +312,7 @@ final class task extends core
), ),
after: 'COLLECT x = worker, y = market, z = task OPTIONS { method: "sorted" }', after: 'COLLECT x = worker, y = market, z = task OPTIONS { method: "sorted" }',
page: (int) $this->session->buffer['operator']['tasks']['page'], page: (int) $this->session->buffer['operator']['tasks']['page'],
sort: 'z.date DESC, z.created DESC, z._key DESC, x.created DESC, y.created DESC, x._key DESC, y._key DESC',
target: empty($search) ? model::COLLECTION : 'registry_tasks', target: empty($search) ? model::COLLECTION : 'registry_tasks',
return: '{task: z, worker: x, market: y}', return: '{task: z, worker: x, market: y}',
binds: empty($search) ? [] : [ binds: empty($search) ? [] : [
@ -448,7 +451,6 @@ final class task extends core
// Перенос времени в дату // Перенос времени в дату
$start = $date->setTime((int) $start->format('H'), (int) $start->format('i')); $start = $date->setTime((int) $start->format('H'), (int) $start->format('i'));
// Запись в буфер // Запись в буфер
$link->task = ['start' => $start] + $link->task; $link->task = ['start' => $start] + $link->task;
} }
@ -469,6 +471,11 @@ final class task extends core
$link->task = ['end' => $end] + $link->task; $link->task = ['end' => $end] + $link->task;
} }
// Инициализация данных заблокировавшего аккаунта
if (isset($link->task['block']) && $link->task['block']['expires'] > time() && $link->task['block']['account'] !== (int) $account->getKey())
$link->task = ['block' => ['_account' => account::read('d._key == "' . $link->task['block']['account'] . '"')] + $link->task['block']] + $link->task;
else $link->task = ['block' => null] + $link->task;
// Инициализация буфера сгенерированных данных работы для шаблонизатора // Инициализация буфера сгенерированных данных работы для шаблонизатора
$generated = []; $generated = [];
@ -642,49 +649,59 @@ final class task extends core
// Найден сотрудник (запрашиваемый для записи сотрудник существует в базе данных) // Найден сотрудник (запрашиваемый для записи сотрудник существует в базе данных)
if (!$worker->fired) { if (!$worker->fired) {
if ($task->worker !== $parameters['worker']) { // Не уволен сотрудник
// Идентификатор запрашиваемого сотрудника не равен актуальному
// Запись сотрудника if (in_array($task->work, $worker->works, true)) {
$task->worker = $worker->id; // Работа соответствует подходящим сотруднику
// Снятие с публикации if ((($age = (new DateTime)->diff(DateTime::createFromFormat('d.m.Y', $worker->birth))->y) > 15 && ($task->work === 'Выкладчик' || $task->work === 'Гастроном')) || $age > 17) {
$task->published = false; // Подходит по возрасту сотрудник
if (_core::update($task)) { if ($task->worker !== $parameters['worker']) {
// Записано изменение в базу данных // Идентификатор запрашиваемого сотрудника не равен актуальному
// Инициализация строки в глобальную переменную шаблонизатора // Запись сотрудника
$this->view->rows = static::preprocessing($this->account, model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1)); $task->worker = $worker->id;
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение) // Снятие с публикации
$this->view->page = null; $task->published = false;
// Запись заголовков ответа if (_core::update($task)) {
header('Content-Type: application/json'); // Записано изменение в базу данных
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода // Инициализация строки в глобальную переменную шаблонизатора
ob_start(); $this->view->rows = static::preprocessing($this->account, model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1));
// Генерация ответа // Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
echo json_encode( $this->view->page = null;
[
'updated' => true,
'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
'errors' => self::parse_only_text($this->errors)
]
);
// Запись заголовков ответа // Запись заголовков ответа
header('Content-Length: ' . ob_get_length()); header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Отправка и деинициализация буфера вывода // Инициализация буфера вывода
ob_end_flush(); ob_start();
flush();
} else throw new exception('Не удалось записать изменения в базу данных'); // Генерация ответа
} else throw new exception('Сотрудник уже назначен'); echo json_encode(
[
'updated' => true,
'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
'errors' => self::parse_only_text($this->errors)
]
);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
} else throw new exception('Не удалось записать изменения в базу данных');
} else throw new exception('Сотрудник уже назначен');
} else throw new exception('Сотрудник не подходит по возрасту');
} else throw new exception('Заявка не подходит по типу работы для сотрудника');
} else throw new exception('Нельзя назначить уволенного сотрудника'); } else throw new exception('Нельзя назначить уволенного сотрудника');
} else throw new exception('Не найден сотрудник'); } else throw new exception('Не найден сотрудник');
} }
@ -834,9 +851,11 @@ final class task extends core
// Инициализация данных сотрудника // Инициализация данных сотрудника
$this->view->task = model::read( $this->view->task = model::read(
'd._key == "' . $parameters['task'] . '"', 'd._key == "' . $parameters['task'] . '"',
return: $this->account->type === 'market' return: match ($this->account->type) {
? '{_key: d._key, created: d.created, updated: d.updated, date: d.date, start: d.start, end: d.end, market: d.market, confirmed: d.confirmed, completed: d.completed }' 'market' => '{_key: d._key, created: d.created, updated: d.updated, date: d.date, start: d.start, end: d.end, market: d.market, confirmed: d.confirmed, completed: d.completed }',
: '{_key: d._key, created: d.created, updated: d.updated, date: d.date, start: d.start, end: d.end, market: d.market, confirmed: d.confirmed, completed: d.completed, hided: d.hided, updates: d.updates }' 'administrator', 'operator' => '{_key: d._key, created: d.created, updated: d.updated, date: d.date, start: d.start, end: d.end, market: d.market, confirmed: d.confirmed, completed: d.completed, hided: d.hided, updates: d.updates }',
default => 'd'
}
)->getAll(); )->getAll();
// Заявка не принадлежит запросившему магазину? // Заявка не принадлежит запросившему магазину?
@ -1020,7 +1039,7 @@ final class task extends core
'tax' => 'ИНН', 'tax' => 'ИНН',
'city' => 'Город', 'city' => 'Город',
'payment' => 'Форма оплаты', 'payment' => 'Форма оплаты',
'works' => 'Формы работ', 'works' => 'Типы работ',
default => $key default => $key
}, },
'value' => $value 'value' => $value
@ -1128,6 +1147,7 @@ final class task extends core
'number' => 'Номер', 'number' => 'Номер',
'mail' => 'Почта', 'mail' => 'Почта',
'commentary' => 'Комментарий', 'commentary' => 'Комментарий',
'bans' => 'Заблокированные',
default => $key default => $key
}, },
'value' => $value 'value' => $value
@ -1694,6 +1714,94 @@ final class task extends core
} }
} }
/**
* Сгенерировать строку
*
* Используется тогда, когда нужно получить HTML-код строки заявки
*
* @param array $parameters Параметры запроса
*
* @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
*/
public function row(array $parameters = []): void
{
try {
if ($this->account->status()) {
// Авторизован аккаунт
// Инициализация строки в глобальную переменную шаблонизатора
if ($this->account->type === 'worker')
$this->view->rows = model::list(before: 'FILTER task._key == "' . $parameters['task'] . '" && task.worker == "' . account::worker($this->account->getId())?->id . '"');
else if ($this->account->type === 'operator')
$this->view->rows = model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"');
else if ($this->account->type === 'market')
$this->view->rows = model::list(before: 'FILTER task._key == "' . $parameters['task'] . '" && task.market == "' . account::market($this->account->getId())?->id . '"');
else if ($this->account->type === 'administrator')
$this->view->rows = model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"');
else $this->view->rows = [];
// Генерация данных для генерации строки
$this->view->rows = static::preprocessing($this->account, $this->view->rows);
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
$this->view->page = null;
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Генерация ответа
echo json_encode(
[
'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
'errors' => self::parse_only_text($this->errors)
]
);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
} else throw new exception('Вы не авторизованы');
} catch (exception $e) {
// Запись в реестр ошибок
$this->errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Генерация ответа
echo json_encode(
[
'errors' => self::parse_only_text($this->errors)
]
);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
}
}
/** /**
* Удалить * Удалить
* *

View File

@ -60,7 +60,7 @@ final class account extends core
$this->document = $account->document; $this->document = $account->document;
// Связь сессии с аккаунтом // Связь сессии с аккаунтом
session::connect($session->getId(), $this->document->getId(), $errors); self::session($session->getId(), $this->document->getId(), $errors);
// Блокировка доступа // Блокировка доступа
if ($account?->active !== true) throw new exception('Свяжитесь с оператором'); if ($account?->active !== true) throw new exception('Свяжитесь с оператором');
@ -96,7 +96,7 @@ final class account extends core
$this->document = $account; $this->document = $account;
// Связь сессии с аккаунтом // Связь сессии с аккаунтом
session::connect($session->getId(), $this->document->getId(), $errors); self::session($session->getId(), $this->document->getId(), $errors);
// Удаление использованных данных из буфера сессии // Удаление использованных данных из буфера сессии
$session->write(['entry' => ['number' => null, 'password' => null]]); $session->write(['entry' => ['number' => null, 'password' => null]]);
@ -129,7 +129,7 @@ final class account extends core
$this->document = $account; $this->document = $account;
// Связь сессии с аккаунтом // Связь сессии с аккаунтом
session::connect($session->getId(), $this->document->getId(), $errors); self::session($session->getId(), $this->document->getId(), $errors);
// Удаление использованных данных из буфера сессии // Удаление использованных данных из буфера сессии
$session->write(['entry' => ['_key' => null, 'password' => null]]); $session->write(['entry' => ['_key' => null, 'password' => null]]);
@ -155,7 +155,7 @@ final class account extends core
$this->document = $account; $this->document = $account;
// Связь сессии с аккаунтом // Связь сессии с аккаунтом
session::connect($session->getId(), $this->document->getId(), $errors); self::session($session->getId(), $this->document->getId(), $errors);
// Удаление использованных данных из буфера сессии // Удаление использованных данных из буфера сессии
$session->write(['entry' => ['_key' => null, 'password' => null]]); $session->write(['entry' => ['_key' => null, 'password' => null]]);
@ -181,7 +181,7 @@ final class account extends core
$this->document = $account; $this->document = $account;
// Связь сессии с аккаунтом // Связь сессии с аккаунтом
session::connect($session->getId(), $this->document->getId(), $errors); self::session($session->getId(), $this->document->getId(), $errors);
// Удаление использованных данных из буфера сессии // Удаление использованных данных из буфера сессии
$session->write(['entry' => ['_key' => null, 'password' => null]]); $session->write(['entry' => ['_key' => null, 'password' => null]]);
@ -318,7 +318,7 @@ final class account extends core
} }
/** /**
* Инициализировать связь аккаунта с сотрудником * Подключить к сотруднику
* *
* Ищет связь аккаунта с сотрудником, если не находит, то создаёт её * Ищет связь аккаунта с сотрудником, если не находит, то создаёт её
* *
@ -328,6 +328,9 @@ final class account extends core
* @param array &$errors Реестр ошибок * @param array &$errors Реестр ошибок
* *
* @return bool Связан аккаунт с сотрудником? * @return bool Связан аккаунт с сотрудником?
*
* @todo
* 1. Переделать на подобие account::session и перенести в mirzaev/ebala/models/worker
*/ */
public static function connect(string $account, string $target, string $type = 'worker', array &$errors = []): bool public static function connect(string $account, string $target, string $type = 'worker', array &$errors = []): bool
{ {
@ -403,6 +406,65 @@ final class account extends core
return null; return null;
} }
/**
* Подключить к сессии
*
* Ищет связь сессии с аккаунтом, если не находит, то создаёт её
*
* @param string $session Идентификатор сессии
* @param string $account Идентификатор аккаунта
* @param array &$errors Реестр ошибок
*
* @return bool Аккаунт подключен к сессии?
*/
public static function session(string $session, string $account, array &$errors = []): bool
{
try {
if (
collection::init(static::$arangodb->session, self::COLLECTION)
&& collection::init(static::$arangodb->session, session::COLLECTION)
&& collection::init(static::$arangodb->session, session::COLLECTION . '_edge_' . self::COLLECTION, true)
) {
// Инициализированы коллекции
if (
collection::search(static::$arangodb->session, sprintf(
<<<AQL
FOR document IN %s
FILTER document._from == '%s' && document._to == '%s'
LIMIT 1
RETURN document
AQL,
session::COLLECTION . '_edge_' . self::COLLECTION,
$session,
$account
)) instanceof _document
|| document::write(static::$arangodb->session, session::COLLECTION . '_edge_' . self::COLLECTION, [
'_from' => $session,
'_to' => $account
])
) {
// Найдено, либо создано ребро: session -> account
// Возврат (успех)
return true;
} else throw new exception('Не удалось создать ребро: session -> account');
} else throw new exception('Не удалось инициализировать коллекции');
} catch (exception $e) {
// Запись в реестр ошибок
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
// Возврат (провал)
return false;
}
/** /**
* Записать * Записать
* *

View File

@ -35,7 +35,7 @@ class core extends model
/** /**
* Путь до файла с настройками подключения к базе данных ArangoDB * Путь до файла с настройками подключения к базе данных ArangoDB
*/ */
final public const ARANGODB = '../settings/arangodb.php'; final public const ARANGODB = __DIR__ . '/../settings/arangodb.php';
/** /**
* Соединение с базой данных ArangoDB * Соединение с базой данных ArangoDB
@ -203,7 +203,8 @@ class core extends model
* *
* @return int|null Количество документов в базе данных, если найдены * @return int|null Количество документов в базе данных, если найдены
*/ */
public static function count(?string $collection = null, array &$errors = []): int|null { public static function count(?string $collection = null, array &$errors = []): int|null
{
try { try {
if (collection::init(static::$arangodb->session, static::COLLECTION)) { if (collection::init(static::$arangodb->session, static::COLLECTION)) {
// Инициализирована коллекция // Инициализирована коллекция

View File

@ -164,67 +164,12 @@ final class session extends core
// Закрыть сессию // Закрыть сессию
} }
/**
* Инициализировать связь сессии с аккаунтом
*
* Ищет связь сессии с аккаунтом, если не находит, то создаёт её
*
* @param account $account Инстанция аккаунта
* @param array &$errors Реестр ошибок
*
* @return bool Связана сессия с аккаунтом?
*/
public static function connect(string $session, string $account, array &$errors = []): bool
{
try {
if (
collection::init(static::$arangodb->session, self::COLLECTION)
&& collection::init(static::$arangodb->session, account::COLLECTION)
&& collection::init(static::$arangodb->session, self::COLLECTION . '_edge_' . account::COLLECTION, true)
) {
// Инициализированы коллекции
if (
collection::search(static::$arangodb->session, sprintf(
<<<AQL
FOR document IN %s
FILTER document._from == '%s' && document._to == '%s'
LIMIT 1
RETURN document
AQL,
self::COLLECTION . '_edge_' . account::COLLECTION,
$session,
$account
)) instanceof _document
|| document::write(static::$arangodb->session, self::COLLECTION . '_edge_' . account::COLLECTION, [
'_from' => $session,
'_to' => $account
])
) {
// Найдено, либо создано ребро: session -> account
return true;
} else throw new exception('Не удалось создать ребро: session -> account');
} else throw new exception('Не удалось инициализировать коллекции');
} catch (exception $e) {
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return false;
}
/** /**
* Найти связанный аккаунт * Найти связанный аккаунт
* *
* @param array &$errors Реестр ошибок * @param array &$errors Реестр ошибок
* *
* @return ?account Инстанция аккаунта, если удалось найти * @return account|null Инстанция аккаунта, если удалось найти
*/ */
public function account(array &$errors = []): ?account public function account(array &$errors = []): ?account
{ {
@ -273,6 +218,7 @@ final class session extends core
]; ];
} }
// Возврат (провал)
return null; return null;
} }

View File

@ -0,0 +1,164 @@
<?php
declare(strict_types=1);
namespace mirzaev\ebala\models;
// Project files
use mirzaev\ebala\models\traits\instance,
mirzaev\ebala\models\traits\status,
mirzaev\ebala\models\account,
mirzaev\ebala\models\worker,
mirzaev\ebala\models\market;
// Библиотека для ArangoDB
use ArangoDBClient\Document as _document;
// Фреймворк ArangoDB
use mirzaev\arangodb\collection,
mirzaev\arangodb\document;
// Встроенные библиотеки
use exception;
/**
* Модель сокета
*
* Управляет записью и чтением настроек сайта из ArangoDB
*
* @package mirzaev\ebala\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class socket extends core
{
use instance, status;
/**
* Collection name in ArangoDB
*/
final public const COLLECTION = 'socket';
/**
* Инстанция документа в базе данных
*/
protected readonly _document $document;
/**
* Подключение к сессии
*
* @param string $session Идентификатор сессии
* @param string $key Ключ для регистрации (из документа в static::COLLECTION)
* @param array &$errors Реестр ошибок
*
* @return bool Сокет подключен к сессии?
*/
public static function session(string $session, string $key, array &$errors = []): bool
{
try {
if (
collection::init(static::$arangodb->session, self::COLLECTION)
&& collection::init(static::$arangodb->session, session::COLLECTION)
&& collection::init(static::$arangodb->session, $edge = session::COLLECTION . '_edge_' . self::COLLECTION, true)
) {
// Инициализирована коллекция
// Чтение сокета
$socket = self::read(
filter: "d.key == \"$key\" && d.expires > DATE_NOW() / 1000",
sort: 'd.created desc, d.expires desc',
amount: 1,
);
if ($socket instanceof _document) {
// Найден сокет
if (document::write(static::$arangodb->session, $edge, [
'_from' => $session,
'_to' => $socket->getId()
])) {
// Записано ребро: СЕССИЯ -> СОКЕТ
// Возврат (успех)
return true;
} else throw new exception('Не удалось записать изменения в базу данных');
} else throw new exception('Не удалось найти сокет');
} else throw new exception('Не удалось инициализировать коллекции');
} catch (exception $e) {
// Write to the errors registry
$errors['socket'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
// Возврат (провал)
return false;
}
/**
* Найти аккаунт
*
* Ищет аккаунт по идентификатору подключения к сокету
*
* @param int $socket Идентификатор подключения к сокету (во внутренней базе данных OpenSwoole)
* @param array &$errors Реестр ошибок
*
* @returnaccount|null Аккаунт, если найден
*/
public static function account(int $socket, array &$errors = []): ?account
{
try {
if (
collection::init(static::$arangodb->session, self::COLLECTION)
&& collection::init(static::$arangodb->session, session::COLLECTION)
&& collection::init(static::$arangodb->session, account::COLLECTION)
&& collection::init(static::$arangodb->session, session::COLLECTION . '_edge_' . self::COLLECTION, true)
&& collection::init(static::$arangodb->session, session::COLLECTION . '_edge_' . account::COLLECTION, true)
) {
// Инициализированы коллекции
// Инициализация инстанции аккаунта
$account = new account;
// Поиск инстанции аккаунта в базе данных
$instance = $account->instance(collection::search(static::$arangodb->session, sprintf(
<<<AQL
FOR a IN 1 OUTBOUND
(FOR s IN 1 INBOUND
(FOR s IN %s
FILTER s.id == %s && s.expires > DATE_NOW() / 1000
SORT s.created desc, s.expires desc
LIMIT 1
RETURN s
)[0]._id
%s
RETURN s
)[0]._id
%s
RETURN a
AQL,
socket::COLLECTION,
$socket,
session::COLLECTION . '_edge_' . socket::COLLECTION,
session::COLLECTION . '_edge_' . account::COLLECTION,
)));
// Возврат (успех)
return $instance instanceof _document ? $account : throw new exception('Не удалось найти инстанцию аккаунта в базе данных');
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в реестр ошибок
$errors['socket'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
// Возврат (провал)
return null;
}
}

View File

@ -294,4 +294,100 @@ final class task extends core
// Exit (fail) // Exit (fail)
return null; return null;
} }
/**
* Block by account
*
* @param int $task (_key)
* @param int $account (_key)
* @param array $errors
*
* @return bool Task is blocked?
*/
public static function block(
int $task,
int $account,
array &$errors = []
): bool {
try {
if (collection::init(static::$arangodb->session, self::COLLECTION)) {
// Инициализирована коллекция
if (($task = static::read('d._key == "' . $task . '"')) instanceof _document) {
// Найдена заявка
if ($task->block === null || $task->block['expires'] < time()) {
// Не заблокирована заявка
// Блокировка
$task->block = ['account' => $account, 'expires' => (int) strtotime('+1 minute')];
// Запись обновления в базу данных и возврат (успех)
return core::update($task);
} else throw new exception('Заявка уже заблокирована: ' . $task->block['account']);
} else throw new exception('Не удалось инициализировать коллекции');
} else throw new exception('Не удалось инициализировать коллекции');
} catch (exception $e) {
// Write to the errors registry
$errors['task'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
// Exit (fail)
return false;
}
/**
* Unblock by account
*
* @param int $task (_key)
* @param int $account (_key)
* @param array $errors
*
* @return bool Task is blocked?
*/
public static function unblock(
int $task,
int $account,
array &$errors = []
): bool {
try {
if (collection::init(static::$arangodb->session, self::COLLECTION)) {
// Инициализирована коллекция
if (($task = static::read('d._key == "' . $task . '"')) instanceof _document) {
// Найдена заявка
if ($task->block !== null) {
// Заблокирована заявка
if ($task->block['account'] === $account || $task->block['expires'] > time()) {
// Аккаунт отменяет свою блокировку или истекло время блокировки
// Разблокировка
$task->block = null;
// Запись обновления в базу данных и возврат (успех)
return core::update($task);
}
} else throw new exception('Заявка не заблокирована');
} else throw new exception('Не удалось инициализировать коллекции');
} else throw new exception('Не удалось инициализировать коллекции');
} catch (exception $e) {
// Write to the errors registry
$errors['task'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
// Exit (fail)
return false;
}
} }

View File

@ -0,0 +1,35 @@
@charset "UTF-8";
#connection {
z-index: 999999;
position: fixed;
bottom: 20px;
left: 20px;
height: 16px;
display: flex;
}
#connection > i#indicator {
width: 16px;
height: 16px;
display: block;
cursor: help;
border-radius: 100%;
}
#connection > small {
margin-left: 7px;
height: 16px;
display: flex;
align-items: center;
font-weight: bold;
color: var(--socket-text);
}
#connection > i#indicator.disconnected:not(.connected) {
background-color: var(--socket-disconnected);
}
#connection > i#indicator.connected:not(.disconnected) {
background-color: var(--socket-connected);
}

0
mirzaev/ebala/system/public/css/fonts/dejavu.css Normal file → Executable file
View File

0
mirzaev/ebala/system/public/css/fonts/fira.css Normal file → Executable file
View File

0
mirzaev/ebala/system/public/css/fonts/hack.css Normal file → Executable file
View File

0
mirzaev/ebala/system/public/css/icons/home.css Normal file → Executable file
View File

View File

0
mirzaev/ebala/system/public/css/icons/smartphone.css Normal file → Executable file
View File

0
mirzaev/ebala/system/public/css/icons/timer.css Normal file → Executable file
View File

0
mirzaev/ebala/system/public/css/icons/user.css Normal file → Executable file
View File

0
mirzaev/ebala/system/public/css/icons/work_alt.css Normal file → Executable file
View File

View File

@ -112,13 +112,14 @@ section.panel.list>div#title>span {
} }
section.panel.list>div.row { section.panel.list>div.row {
--width: calc(100% - 24px);
--gap: 12px; --gap: 12px;
--background: var(--cloud); --background: var(--cloud);
position: relative; position: relative;
left: 0px; left: 0px;
width: calc(100% - 24px);
height: 35px; height: 35px;
display: flex; display: flex;
width: var(--width, calc(100% - 24px));
gap: var(--gap, 12px); gap: var(--gap, 12px);
padding: 0 var(--gap, 12px); padding: 0 var(--gap, 12px);
border-radius: 0px; border-radius: 0px;
@ -138,7 +139,7 @@ section.panel.list>div.row:not(:nth-of-type(1))>span {
-moz-box-shadow: var(--box-shadow); -moz-box-shadow: var(--box-shadow);
} }
section.panel.list>div.row:not(:nth-of-type(1)):is(:hover, :focus) { section.panel.list>div.row:not(:nth-of-type(1)):not([data-blocked]):is(:hover, :focus) {
--padding-left: 24px; --padding-left: 24px;
--padding-right: 24px; --padding-right: 24px;
left: -12px; left: -12px;
@ -281,7 +282,7 @@ section.panel.list>div.row>span.field {
cursor: text; cursor: text;
} }
section.panel.list>div.row:not(:nth-of-type(1))>span:is(.important, .interactive:is(:hover, :focus)) { section.panel.list>div.row:not(:nth-of-type(1)):not([data-blocked])>span:is(.important, .interactive:is(:hover, :focus)) {
--margin: calc(var(--gap) / 2); --margin: calc(var(--gap) / 2);
--border-left: calc(var(--padding-left, var(--margin, 0px)) * -1); --border-left: calc(var(--padding-left, var(--margin, 0px)) * -1);
--border-right: var(--padding-right, var(--margin, 0px)); --border-right: var(--padding-right, var(--margin, 0px));
@ -482,3 +483,29 @@ section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)).fired>span:is
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)):nth-child(2n + 1).fired>span:is(.important, .interactive:is(:hover, :focus)) { section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)):nth-child(2n + 1).fired>span:is(.important, .interactive:is(:hover, :focus)) {
--background: var(--magma-important-above); --background: var(--magma-important-above);
} }
section.panel.list>div.row[data-blocked] {
margin-left: 8px;
width: calc(var(--width) - 16px);
cursor: progress;
opacity: 70% !important;
}
section.panel.list>div.row[data-blocked]:before {
content: attr(data-blocked) !important;
color: var(--earth-text-important-below);
}
section.panel.list>div.row[data-blocked] * {
pointer-events: none;
}
section.panel.list>div.row:not([data-blocked]):has(+ div.row[data-blocked]) {
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
}
section.panel.list>div.row[data-blocked]+div.row:not([data-blocked]) {
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}

0
mirzaev/ebala/system/public/css/popup.css Normal file → Executable file
View File

View File

@ -167,6 +167,10 @@
--link: #3c76ff; --link: #3c76ff;
--link-hover: #6594ff; --link-hover: #6594ff;
--link-active: #3064dd; --link-active: #3064dd;
--socket-connected: #2be851;
--socket-disconnected: #8e8181;
--socket-text: #b09999;
} }
body { body {

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

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