4 Commits

190 changed files with 8971 additions and 5188 deletions
README.mdcomposer.jsoncomposer.lockebala-socket.service
mirzaev/ebala/system
controllers
models
public
css
fonts
dejavu
fira
FiraMono-Bold.woffFiraMono-Bold.woff2FiraMono-Medium.woffFiraMono-Medium.woff2FiraMono-Regular.woffFiraMono-Regular.woff2FiraSans-Bold.woffFiraSans-Bold.woff2FiraSans-BoldItalic.woffFiraSans-BoldItalic.woff2FiraSans-Book.woffFiraSans-Book.woff2FiraSans-BookItalic.woffFiraSans-BookItalic.woff2FiraSans-Eight.woffFiraSans-Eight.woff2FiraSans-EightItalic.woffFiraSans-EightItalic.woff2FiraSans-ExtraBold.woffFiraSans-ExtraBold.woff2FiraSans-ExtraBoldItalic.woffFiraSans-ExtraBoldItalic.woff2FiraSans-ExtraLight.woffFiraSans-ExtraLight.woff2FiraSans-ExtraLightItalic.woffFiraSans-ExtraLightItalic.woff2FiraSans-Four.woffFiraSans-Four.woff2FiraSans-FourItalic.woffFiraSans-FourItalic.woff2FiraSans-Hair.woffFiraSans-Hair.woff2FiraSans-HairItalic.woffFiraSans-HairItalic.woff2FiraSans-Heavy.woffFiraSans-Heavy.woff2FiraSans-HeavyItalic.woffFiraSans-HeavyItalic.woff2FiraSans-Italic.woffFiraSans-Italic.woff2FiraSans-Light.woffFiraSans-Light.woff2FiraSans-LightItalic.woffFiraSans-LightItalic.woff2FiraSans-Medium.woffFiraSans-Medium.woff2FiraSans-MediumItalic.woffFiraSans-MediumItalic.woff2FiraSans-Regular.woffFiraSans-Regular.woff2FiraSans-SemiBold.woffFiraSans-SemiBold.woff2FiraSans-SemiBoldItalic.woffFiraSans-SemiBoldItalic.woff2FiraSans-Thin.woffFiraSans-Thin.woff2FiraSans-ThinItalic.woffFiraSans-ThinItalic.woff2FiraSans-Two.woffFiraSans-Two.woff2FiraSans-TwoItalic.woffFiraSans-TwoItalic.woff2FiraSans-Ultra.woffFiraSans-Ultra.woff2FiraSans-UltraItalic.woffFiraSans-UltraItalic.woff2FiraSans-UltraLight.woffFiraSans-UltraLight.woff2FiraSans-UltraLightItalic.woffFiraSans-UltraLightItalic.woff2
hack
index.php
js
socket.php
sounds
views

@@ -1,7 +1,6 @@
# Ebala
Site-registry of tasks for outsourced employees
From this project i earned **700 000** Russian rubles</br>
*As long as commits appear in the repository, this means that i continue paid development*</br>
From this project i earned >**1 000 000** Russian rubles</br>
**DEVELOPMENT COMPLETED. PROJECT CLOSED.**</br>
</br>
I am selling this site to **capitalist scum** for a lot of money, but you, friend, can use my code **for free** ✌️

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

400
composer.lock generated

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

17
ebala-socket.service Executable 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

@@ -8,6 +8,7 @@ namespace mirzaev\ebala\controllers;
use mirzaev\ebala\controllers\core,
mirzaev\ebala\controllers\traits\errors,
mirzaev\ebala\models\account as model,
mirzaev\ebala\models\task,
mirzaev\ebala\models\registry,
mirzaev\ebala\models\core as _core;
@@ -27,6 +28,37 @@ final class account extends core
{
use errors;
/**
* Страница аккаунта
*
* @param array $parameters Параметры запроса
*/
public function index(array $parameters = []): ?string
{
if ($this->account->status()) {
// Авторизован аккаунт
// Инициализация истории заявок
$this->view->history = task::list(before: 'FILTER task.worker == "' . model::worker($this->account->getId())?->id . '"');
// Инициализация баланса счёта
// В будущем сделать перебор по всем связанным с аккаунтам сотрудникам и магазинам (сейчас только 1 сотрудник может быть)
$this->view->balance = 0;
foreach (task::list(before: 'FILTER task.worker == "' . model::worker($this->account->getId())?->id . '" && task.result.processed == false') as $task) $this->view->balance += $task->task['result']['hours'] * $task->task['result']['hour'] + $task->task['result']['penalty'] + $task->task['result']['bonus'];
// Генерация представления
$main = $this->view->render(DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'account.html');
} else $main = $this->authorization();
// Возврат (успех)
if ($_SERVER['REQUEST_METHOD'] === 'GET') return $this->view->render(DIRECTORY_SEPARATOR . 'index.html', ['main' => $main]);
else if ($_SERVER['REQUEST_METHOD'] === 'POST') return $main;
// Возврат (провал)
return null;
}
/**
* Прочитать данные
*
@@ -38,7 +70,7 @@ final class account extends core
// Авторизован аккаунт администратора или оператора
// Инициализация данных аккаунта
$account = model::read('d._key == "' . $parameters['id'] . '"', return: '{ name: d.name, number: d.number, mail: d.mail, commentary: d.commentary }')->getAll();
$account = model::read('d._key == "' . $parameters['id'] . '"', return: '{ name: d.name, number: d.number, mail: d.mail, commentary: d.commentary, transactions: d.transactions }')->getAll();
if (!empty($account)) {
// Найдены данные аккаунта
@@ -74,6 +106,7 @@ final class account extends core
*/
public function update(array $parameters = []): ?string
{
try {
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
// Авторизован аккаунт администратора или оператора
@@ -102,6 +135,12 @@ final class account extends core
);
else throw new exception('Пароль должен быть длиннее 6 символов');
if ($parameters['commentary'] !== $account->commentary) $account->commentary = $parameters['commentary'];
if (!empty($parameters['transactions']) && $parameters['transactions'] !== $account->transactions)
$account->transactions = match ($parameters['transactions']) {
'true' => true,
'false' => false,
default => false
};
if (_core::update($account)) {
// Записаны данные аккаунта
@@ -184,6 +223,40 @@ final class account extends core
} 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;
@@ -196,6 +269,7 @@ final class account extends core
*/
public function delete(array $parameters = []): ?string
{
try {
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
// Авторизован аккаунт администратора или оператора
@@ -264,6 +338,40 @@ final class account extends core
} 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;
@@ -276,6 +384,7 @@ final class account extends core
*/
public function ban(array $parameters = []): ?string
{
try {
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
// Авторизован аккаунт администратора или оператора
@@ -315,6 +424,40 @@ final class account extends core
} 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;
@@ -327,6 +470,7 @@ final class account extends core
*/
public function unban(array $parameters = []): ?string
{
try {
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
// Авторизован аккаунт администратора или оператора
@@ -366,6 +510,40 @@ final class account extends core
} 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;

@@ -147,8 +147,13 @@ final class administrator extends core
$search_query,
empty($filters) ? null : " && ($filters)"
),
after: <<<AQL
COLLECT x = account OPTIONS { method: "sorted" }
AQL,
page: (int) $this->session->buffer[$_SERVER['INTERFACE']]['administrators']['page'],
sort: 'x.created DESC, x._key DESC',
target: empty($search) ? account::COLLECTION : 'registry_accounts',
return: '{account: x}',
binds: empty($search) ? [] : [
'search' => $search
]
@@ -229,12 +234,35 @@ final class administrator extends core
if (empty($account)) throw new exception('Не удалось создать аккаунт');
} catch (exception $e) {
// Write to the errors registry
$this->errors['account'][] = [
$this->errors['administrator'][] = [
'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();
}
// Инициализация идентификатора аккаунта (ключ документа инстанции аккаунта в базе данных)

@@ -56,7 +56,7 @@ class core extends controller
public function __construct(bool $initialize = true)
{
// Блокировка запросов от 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);

@@ -180,12 +180,15 @@ final class market extends core
FILTER account.type == 'market' && b.deleted != true
%s
%s
COLLECT x = account, y = market OPTIONS { method: "sorted" }
AQL,
empty($filters_before) ? null : "FILTER $filters_before",
empty($filters_after) ? null : "FILTER $filters_after"
),
page: (int) $this->session->buffer[$_SERVER['INTERFACE']]['markets']['page'],
sort: 'x.created DESC, y.created DESC, x._key DESC, y._key DESC',
target: empty($search) ? account::COLLECTION : 'registry_accounts',
return: '{account: x, market: y}',
binds: empty($search) ? [] : [
'search' => $search
]
@@ -338,7 +341,7 @@ final class market extends core
throw new exception('Не инициализирован аккаунт');
} catch (exception $e) {
// Write to the errors registry
$this->errors['account'][] = [
$this->errors['market'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@@ -388,6 +391,267 @@ final class market extends core
return null;
}
/**
* Заблокировать сотрудника в магазине
*
* @param array $parameters Параметры запроса
*/
public function ban(array $parameters = []): ?string
{
try {
if ($this->account->status() && $this->account->type === 'market') {
// Авторизован аккаунт магазина
// Инициализация данных магазина
$market = account::market($this->account->getId());
if ($market instanceof _document) {
// Найден магазин
// Блокировка сотрудника
if (!in_array($parameters['worker'], $market->bans ??= [], true)) $market->bans = $market->bans + [$parameters['worker']];
if (_core::update($market)) {
// Записаны данные магазина
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
$this->view->page = null;
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Инициализация буфера ответа
$return = [
'banned' => 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('Не удалось записать изменения в базу данных');
} 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();
}
// Возврат (провал)
return null;
}
/**
* Разблокировать сотрудника в магазине
*
* @param array $parameters Параметры запроса
*/
public function unban(array $parameters = []): ?string
{
try {
if ($this->account->status() && $this->account->type === 'market') {
// Авторизован аккаунт магазина
// Инициализация данных магазина
$market = account::market($this->account->getId());
if ($market instanceof _document) {
// Найден магазин
// Разблокировка сотрудника
if (in_array($parameters['worker'], $market->bans ??= [], true)) $market->bans = array_diff($market->bans ??= [], [$parameters['worker']]);
if (_core::update($market)) {
// Записаны данные магазина
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
$this->view->page = null;
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Инициализация буфера ответа
$return = [
'unbanned' => 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('Не удалось записать изменения в базу данных');
} 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();
}
// Возврат (провал)
return null;
}
/**
* Проверить наличие сотрудника в реестре заблокированных
*
* @param array $parameters Параметры запроса
*/
public function banned(array $parameters = []): ?string
{
try {
if ($this->account->status() && $this->account->type === 'market') {
// Авторизован аккаунт магазина
// Инициализация данных магазина
$market = account::market($this->account->getId());
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('Не удалось найти магазин');
}
} 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;
}
/**
* Обновить данные
*
@@ -395,6 +659,7 @@ final class market extends core
*/
public function update(array $parameters = []): ?string
{
try {
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
// Авторизован аккаунт администратора или оператора
@@ -416,6 +681,7 @@ final class market extends core
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)) {
// Записаны данные магазина
@@ -465,6 +731,39 @@ final class market extends core
} 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();
}
// Возврат (провал)
return null;

@@ -147,8 +147,13 @@ final class operator extends core
$search_query,
empty($filters) ? null : " && ($filters)"
),
after: <<<AQL
COLLECT x = account OPTIONS { method: "sorted" }
AQL,
page: (int) $this->session->buffer[$_SERVER['INTERFACE']]['operators']['page'],
sort: 'x.created DESC, x._key DESC',
target: empty($search) ? account::COLLECTION : 'registry_accounts',
return: '{account: x}',
binds: empty($search) ? [] : [
'search' => $search
]
@@ -228,12 +233,35 @@ final class operator extends core
if (empty($account)) throw new exception('Не удалось создать аккаунт');
} catch (exception $e) {
// Write to the errors registry
$this->errors['account'][] = [
$this->errors['operator'][] = [
'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();
}
// Инициализация идентификатора аккаунта (ключ документа инстанции аккаунта в базе данных)

@@ -34,8 +34,8 @@ final class payments extends core
public function workers(array $parameters = []): void
{
try {
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
// Авторизован аккаунт администратора или оператора
if ($this->account->status() && ($this->account->type === 'administrator' || ($this->account->type === 'operator' && $this->account->transactions))) {
// Авторизован аккаунт администратора или оператора (с доступом к транзакциям)
// Инициализация буфера ошибок
$this->errors['export'] ??= [];
@@ -76,7 +76,7 @@ final class payments extends core
} else throw new exception('Вы не авторизованы');
} catch (exception $e) {
// Запись в реестр ошибок
$this->errors[] = [
$this->errors['export'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@@ -110,7 +110,7 @@ final class payments extends core
/**
* Магазины
*
* Расчитать ... (сверку?) за выбранный период и сгенерировать excel-документ
* Расчитать прибыль с магазинов за выбранный период и сгенерировать excel-документ
*
* @param array $parameters Параметры запроса
*
@@ -119,8 +119,8 @@ final class payments extends core
public function markets(array $parameters = []): void
{
try {
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
// Авторизован аккаунт администратора или оператора
if ($this->account->status() && ($this->account->type === 'administrator' || ($this->account->type === 'operator' && $this->account->transactions))) {
// Авторизован аккаунт администратора или оператора (с доступом к транзакциям)
// Инициализация буфера ошибок
$this->errors['export'] ??= [];
@@ -161,7 +161,7 @@ final class payments extends core
} else throw new exception('Вы не авторизованы');
} catch (exception $e) {
// Запись в реестр ошибок
$this->errors[] = [
$this->errors['export'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@@ -192,4 +192,74 @@ final class payments extends core
}
}
/**
* Подтвердить
*
* Подтвердить выполнение операций с документом (магазины или сотрудники)
*
* @param array $parameters Параметры запроса
*
* @return void
*/
public function confirm(array $parameters = []): void
{
try {
if ($this->account->status() && ($this->account->type === 'administrator' || ($this->account->type === 'operator' && $this->account->transactions))) {
// Авторизован аккаунт администратора или оператора (с доступом к транзакциям)
// Инициализация буфера ошибок
$this->errors['confirm'] ??= [];
if (!empty($from = (int) ($_COOKIE["tasks_filter_from"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters']['from']))) {
// Инициализирован параметр: from
if (!empty($to = (int) ($_COOKIE["tasks_filter_to"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters']['to']))) {
// Инициализирован параметр: to
// Запуск процедуры подтверждения
model::confirm(
$from,
$to,
match ($parameters['type']) {
'workers' => 'workers',
'markets' => 'markets',
default => throw new exception('Для подтверждения обработки документа необходимо передать его тип: workers, markets')
},
$this->errors['confirm']
);
} else throw new exception('Не инициализирован параметр: to');
} else throw new exception('Не инициализирован параметр: from');
} else throw new exception('Вы не авторизованы');
} catch (exception $e) {
// Запись в реестр ошибок
$this->errors['confirm'][] = [
'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();
}
}
}

@@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace mirzaev\ebala\controllers;
// Файлы проекта
use mirzaev\ebala\controllers\core,
mirzaev\ebala\models\settings as model,
mirzaev\ebala\models\core as _core;
// Библиотека для ArangoDB
use ArangoDBClient\Document as _document;
// System libraries
use datetime,
datetimezone,
exception;
/**
* Контроллер настроек сайта
*
* @package mirzaev\ebala\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class settings extends core
{
/**
* Страница настроек
*
* @param array $parameters Параметры запроса
*/
public function index(array $parameters = []): ?string
{
if ($this->account->status() && $this->account->type === 'administrator') {
// Авторизован аккаунт (администратор)
// Чтение настроек
$this->view->settings = model::search(
before: 'FILTER setting.category != null'
);
// Генерация представления
$main = $this->view->render(DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'settings.html');
// Возврат (успех)
if ($_SERVER['REQUEST_METHOD'] === 'GET') return $this->view->render(DIRECTORY_SEPARATOR . 'index.html', ['main' => $main]);
else if ($_SERVER['REQUEST_METHOD'] === 'POST') return $main;
}
// Возврат (провал)
return null;
}
/**
* Записать
*
* Записывает (обновляет) в ArangoDB
*
* @param array $parameters Параметры запроса
*
* @return void
*/
public function write(array $parameters = []): void
{
try {
if ($this->account->status() && $this->account->type === 'administrator') {
// Авторизован аккаунт администратора
// Инициализация инстанции настройки
$setting = model::read('d._key == "' . $parameters['id'] . '"');
if ($setting instanceof _document) {
// Найдена инстанция настройки
// Запись значения
$setting->value = $parameters['value'];
if (_core::update($setting)) {
// Записано в ArangoDB
} else throw new exception('Не удалось обновить заявку');
} else throw new exception('Не найдена заявка');
} else throw new exception('Вы не авторизованы');
} catch (exception $e) {
// Запись в реестр ошибок
$this->errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
}
}

@@ -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;
}
}

@@ -272,8 +272,11 @@ final class task extends core
$search_query,
empty($filters) ? null : " && ($filters)"
),
after: 'COLLECT x = worker, y = market, z = task OPTIONS { method: "sorted" }',
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',
return: '{task: z, worker: x, market: y}',
binds: ['worker' => account::worker($this->account->getId())?->id] + (empty($search) ? [] : ['search' => $search])
);
} else if ($_SERVER['INTERFACE'] === 'market') {
@@ -288,8 +291,11 @@ final class task extends core
$search_query,
empty($filters) ? null : " && ($filters)"
),
after: 'COLLECT x = worker, y = market, z = task OPTIONS { method: "sorted" }',
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',
return: '{task: z, worker: x, market: y}',
binds: ['market' => account::market($this->account->getId())?->id] + (empty($search) ? [] : ['search' => $search])
);
} else if ($_SERVER['INTERFACE'] === 'operator') {
@@ -304,8 +310,11 @@ final class task extends core
$search_query,
empty($filters) ? null : " && ($filters)"
),
after: 'COLLECT x = worker, y = market, z = task OPTIONS { method: "sorted" }',
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',
return: '{task: z, worker: x, market: y}',
binds: empty($search) ? [] : [
'search' => $search
]
@@ -322,8 +331,11 @@ final class task extends core
$search_query,
empty($filters) ? null : " && ($filters)"
),
after: 'COLLECT x = worker, y = market, z = task OPTIONS { method: "sorted" }',
page: (int) $this->session->buffer['administrator']['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',
return: '{task: z, worker: x, market: y}',
binds: empty($search) ? [] : [
'search' => $search
]
@@ -439,7 +451,6 @@ final class task extends core
// Перенос времени в дату
$start = $date->setTime((int) $start->format('H'), (int) $start->format('i'));
// Запись в буфер
$link->task = ['start' => $start] + $link->task;
}
@@ -460,6 +471,11 @@ final class task extends core
$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 = [];
@@ -558,15 +574,15 @@ final class task extends core
$end = $date->setTime((int) $end->format('H'), (int) $end->format('i'))->format('U');
// Заявка уже начата?
if (time() - $start > 0)
if (time() - $start > 0 && time() - $end < 0)
throw new exception('Запрещено редактировать начатую заявку');
// Заявка уже прошла?
if (time() - $end > 0)
else if (time() - $end > 0 && $task->completed !== true)
throw new exception('Запрещено редактировать прошедшую заявку');
// Заявка уже завершена?
if ($task->completed === true)
else if ($task->completed === true)
throw new exception('Запрещено редактировать завершённую заявку');
// Прошло более 30 минут после создания заявки? (1800 секунд = 30 минут)
@@ -632,6 +648,15 @@ final class task extends core
if (($worker = worker::read('d.id == "' . $parameters['worker'] . '" && d.active == true', amount: 1)) instanceof _document) {
// Найден сотрудник (запрашиваемый для записи сотрудник существует в базе данных)
if (!$worker->fired) {
// Не уволен сотрудник
if (in_array($task->work, $worker->works, true)) {
// Работа соответствует подходящим сотруднику
if ((($age = (new DateTime)->diff(DateTime::createFromFormat('d.m.Y', $worker->birth))->y) > 15 && ($task->work === 'Выкладчик' || $task->work === 'Гастроном')) || $age > 17) {
// Подходит по возрасту сотрудник
if ($task->worker !== $parameters['worker']) {
// Идентификатор запрашиваемого сотрудника не равен актуальному
@@ -675,6 +700,9 @@ final class task extends core
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 if (!empty($parameters['market'])) {
@@ -823,9 +851,11 @@ final class task extends core
// Инициализация данных сотрудника
$this->view->task = model::read(
'd._key == "' . $parameters['task'] . '"',
return: $this->account->type === '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 }'
return: match ($this->account->type) {
'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 }',
'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();
// Заявка не принадлежит запросившему магазину?
@@ -861,7 +891,7 @@ final class task extends core
// Перевод ключей на русский язык
foreach ($this->view->task as $key => $value)
if ($key === 'updates')
foreach ($value as $key => $value) $buffer['updates'][$key] = [
foreach ($value ?? [] as $key => $value) $buffer['updates'][$key] = [
'label' => match ($key) {
'operator' => 'Оператор',
'market' => 'Магазин',
@@ -869,7 +899,9 @@ final class task extends core
'worker' => 'Сотрудник',
default => $key
},
'value' => $value
'value' => [
'id' => $value
] + account::read("d._key == \"$value\"", amount: 1)?->name ?? []
];
else if (match ($key) {
'created', 'updated', 'confirmed', 'hided', 'completed', '_key' => true,
@@ -1007,7 +1039,7 @@ final class task extends core
'tax' => 'ИНН',
'city' => 'Город',
'payment' => 'Форма оплаты',
'works' => 'Формы работ',
'works' => 'Типы работ',
default => $key
},
'value' => $value
@@ -1115,6 +1147,7 @@ final class task extends core
'number' => 'Номер',
'mail' => 'Почта',
'commentary' => 'Комментарий',
'bans' => 'Заблокированные',
default => $key
},
'value' => $value
@@ -1234,7 +1267,7 @@ final class task extends core
// Генерация ответа
echo json_encode(
[
'confirmed' => $this->view->rows[0]->task['confirmed'],
'confirmed' => $this->view->rows[0]->task['confirmed'] ?? null,
'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
'errors' => self::parse_only_text($this->errors)
]
@@ -1473,31 +1506,41 @@ final class task extends core
// Иниализация сотрудника
$worker = worker::read('d.id == "' . $task->worker . '"');
if ($worker instanceof _document) {
// Найден сотрудник
// Инициализация магазина
$market = market::read('d.id == "' . $task->market . '"');
if ($market instanceof _document) {
// Найден магазин
// Подсчёт часов работы
$hours = model::hours($task->start, $task->end, $this->errors);
// Инициализация цены работы за 1 час
$hour = payments::hour($market->city, $task->work);
// Инициализация цены работы сотрудника за 1 час
$hour = payments::hour('worker', $market->city, $task->work);
// Подсчёт оплаты за работу
$payment = $hour * $hours;
// Инициализация штрафа
$penalty = payments::penalty($task->rating ?? null);
if ($penalty === null) $penalty = -$payment;
// Инициализация премии
$bonus = payments::bonus($task->rating ?? null);
// Инициализация транзакции к оплате сотруднику
model::transaction(
$task->getId(),
$worker->getId(),
$payment - ($penalty === null ? $payment : -$penalty) + $bonus,
$this->errors
);
// Запись результатов
$task->result = [
'hours' => $hours,
'hour' => $hour,
'penalty' => $penalty,
'bonus' => $bonus,
'processed' => false
];
}
}
}
// Запись в реcстре последних обновивших
@@ -1623,7 +1666,7 @@ final class task extends core
// Генерация ответа
echo json_encode(
[
'hided' => $this->view->rows[0]->task['hided'],
'hided' => $this->view->rows[0]->task['hided'] ?? null,
'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
'errors' => self::parse_only_text($this->errors)
]
@@ -1671,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();
}
}
/**
* Удалить
*
@@ -1709,15 +1840,15 @@ final class task extends core
$end = $date->setTime((int) $end->format('H'), (int) $end->format('i'))->format('U');
// Заявка уже начата?
if (time() - $start > 0)
if (time() - $start > 0 && time() - $end < 0)
throw new exception('Запрещено удалять начатую заявку');
// Заявка уже прошла?
if (time() - $end > 0)
else if (time() - $end > 0 && $task->completed !== true)
throw new exception('Запрещено удалять прошедшую заявку');
// Заявка уже завершена?
if ($task->completed === true)
else if ($task->completed === true)
throw new exception('Запрещено удалять завершённую заявку');
// Прошло более 30 минут после создания заявки? (1800 секунд = 30 минут)
@@ -1916,7 +2047,8 @@ final class task extends core
$this->view->works = static::WORKS;
// Проверка на существование записанной в задаче работы в списке существующих работ и запись об этом в глобальную переменную шаблонизатора
foreach ($this->view->works as $work) if ($this->view->task->work === $work) $this->view->exist = true;
foreach ($this->view->works as $work)
if (in_array($work, $this->view->worker->works)) $this->view->exist = true;
// Запись заголовков ответа
header('Content-Type: application/json');
@@ -1962,7 +2094,8 @@ final class task extends core
$this->view->works = static::WORKS;
// Проверка на существование записанной в задаче работы в списке существующих работ и запись об этом в глобальную переменную шаблонизатора
foreach ($this->view->works as $work) if ($this->view->worker->work === $work) $this->view->exist = true;
foreach ($this->view->works as $work)
if (in_array($work, $this->view->worker->works)) $this->view->exist = true;
// Запись заголовков ответа
header('Content-Type: application/json');
@@ -2092,15 +2225,15 @@ final class task extends core
$end = $date->setTime((int) $end->format('H'), (int) $end->format('i'))->format('U');
// Заявка уже начата?
if (time() - $start > 0)
if (time() - $start > 0 && time() - $end < 0)
throw new exception('Запрещено редактировать тип работы начатой заявки');
// Заявка уже прошла?
if (time() - $end > 0)
else if (time() - $end > 0 && $task->completed !== true)
throw new exception('Запрещено редактировать тип работы прошедшей заявки');
// Заявка уже завершена?
if ($task->completed === true)
else if ($task->completed === true)
throw new exception('Запрещено редактировать тип работы завершённой заявки');
// Прошло более 30 минут после создания заявки? (1800 секунд = 30 минут)
@@ -2348,15 +2481,15 @@ final class task extends core
$end = $date->setTime((int) $end->format('H'), (int) $end->format('i'))->format('U');
// Заявка уже начата?
if (time() - $start > 0)
if (time() - $start > 0 && time() - $end < 0)
throw new exception('Запрещено редактировать дату и время начатой заявки');
// Заявка уже прошла?
if (time() - $end > 0)
else if (time() - $end > 0 && $task->completed !== true)
throw new exception('Запрещено редактировать дату и время прошедшей заявки');
// Заявка уже завершена?
if ($task->completed === true)
else if ($task->completed === true)
throw new exception('Запрещено редактировать дату и время завершённой заявки');
// Прошло более 30 минут после создания заявки? (1800 секунд = 30 минут)

@@ -182,8 +182,7 @@ final class worker extends core
|| (LENGTH(@search) > 4 && LEVENSHTEIN_MATCH(a.requisites, TOKENS(@search, 'text_ru')[0], 1, true))
|| (LENGTH(@search) > 7 && LEVENSHTEIN_MATCH(a.tax, TOKENS(@search, 'text_ru')[0], 1, true))
OPTIONS { collections: ["account", "worker"] }
AQL;
AQL;;
// Инициализация данных для генерации HTML-документа с таблицей
$this->view->rows = registry::workers(
before: sprintf(
@@ -200,12 +199,15 @@ final class worker extends core
FILTER account.type == 'worker' && b.deleted != true
%s
%s
COLLECT x = account, y = worker OPTIONS { method: "sorted" }
AQL,
empty($filters_before) ? null : "FILTER $filters_before",
empty($filters_after) ? null : "FILTER $filters_after"
),
page: (int) $this->session->buffer[$_SERVER['INTERFACE']]['workers']['page'],
sort: 'x.created DESC, y.created DESC, x._key DESC, y._key DESC',
target: empty($search) ? account::COLLECTION : 'registry_accounts',
return: '{account: x, worker: y}',
binds: empty($search) ? [] : [
'search' => $search
]
@@ -266,7 +268,7 @@ final class worker extends core
if (!empty($parameters['requisites']) && $parameters['worker_requisites'][-1] === '.') $parameters['worker_requisites'] .= '.';
if (!empty($parameters['worker_birth'])) $parameters['worker_birth'] = DateTime::createFromFormat('Y-m-d', $parameters['worker_birth'])->getTimestamp();
if (!empty($parameters['worker_issued'])) $parameters['worker_issued'] = DateTime::createFromFormat('Y-m-d', $parameters['worker_issued'])->getTimestamp();
if (!empty($parameters['work'])) $parameters['work'] = in_array($parameters['work'], static::WORKS) ? $parameters['work'] : static::WORKS[0];
if (!empty($parameters['works'])) $parameters['works'] = in_array($parameters['works'], static::WORKS) ? $parameters['works'] : static::WORKS[0];
if (!empty($parameters['worker_hiring'])) $parameters['worker_hiring'] = DateTime::createFromFormat('Y-m-d', $parameters['worker_hiring'])->getTimestamp();
// Создание аккаунта
@@ -396,7 +398,7 @@ final class worker extends core
// Авторизован аккаунт администратора или оператора
// Инициализация данных сотрудника
$worker = model::read('d.id == "' . urldecode($parameters['id']) . '"', return: '{ name: d.name, number: d.number, mail: d.mail, birth: d.birth, passport: d.passport, issued: d.issued, department: d.department, requisites: d.requisites, payment: d.payment, tax: d.tax, city: d.city, district: d.district, address: d.address, worl: d.work, hiring: d.hiring}')->getAll();
$worker = model::read('d.id == "' . urldecode($parameters['id']) . '"', return: '{ name: d.name, number: d.number, mail: d.mail, birth: d.birth, passport: d.passport, issued: d.issued, department: d.department, requisites: d.requisites, payment: d.payment, tax: d.tax, city: d.city, district: d.district, address: d.address, worl: d.work, hiring: d.hiring, registration: d.registration}')->getAll();
if (!empty($worker)) {
// Найдены данные сотрудника
@@ -444,8 +446,12 @@ final class worker extends core
// Универсализация
if (!empty($parameters['birth'])) $parameters['birth'] = DateTime::createFromFormat('Y-m-d', $parameters['birth'])->getTimestamp();
if (!empty($parameters['issued'])) $parameters['issued'] = DateTime::createFromFormat('Y-m-d', $parameters['issued'])->getTimestamp();
if (!empty($parameters['work'])) $parameters['work'] = in_array($parameters['work'], static::WORKS) ? $parameters['work'] : static::WORKS[0];
if (!empty($parameters['hiring'])) $parameters['hiring'] = DateTime::createFromFormat('Y-m-d', $parameters['hiring'])->getTimestamp();
if (!empty($buffer = explode(':', $parameters['works']))) {
$parameters['works'] = [];
foreach ($buffer ?? [] as $work)
if (in_array($work, static::WORKS)) array_push($parameters['works'], $work);
}
// Инициализация параметров (перезапись переданными значениями)
if ($parameters['name_first'] !== $worker->name['first']) $worker->name = ['first' => $parameters['name_first']] + $worker->name;
@@ -466,8 +472,9 @@ final class worker extends core
if ($parameters['city'] !== $worker->city) $worker->city = $parameters['city'];
if ($parameters['district'] !== $worker->district) $worker->district = $parameters['district'];
if ($parameters['address'] !== $worker->address) $worker->address = $parameters['address'];
if ($parameters['work'] !== $worker->work) $worker->work = $parameters['work'];
if ($parameters['works'] !== $worker->works) $worker->works = $parameters['works'];
if ($parameters['hiring'] !== $worker->hiring) $worker->hiring = $parameters['hiring'];
if ($parameters['registration'] !== $worker->registration) $worker->registration = $parameters['registration'];
if (_core::update($worker)) {
// Записаны данные сотрудника

@@ -60,7 +60,7 @@ final class account extends core
$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('Свяжитесь с оператором');
@@ -96,7 +96,7 @@ final class account extends core
$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]]);
@@ -129,7 +129,7 @@ final class account extends core
$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]]);
@@ -155,7 +155,7 @@ final class account extends core
$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]]);
@@ -181,7 +181,7 @@ final class account extends core
$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]]);
@@ -318,7 +318,7 @@ final class account extends core
}
/**
* Инициализировать связь аккаунта с сотрудником
* Подключить к сотруднику
*
* Ищет связь аккаунта с сотрудником, если не находит, то создаёт её
*
@@ -328,6 +328,9 @@ final class account extends core
* @param array &$errors Реестр ошибок
*
* @return bool Связан аккаунт с сотрудником?
*
* @todo
* 1. Переделать на подобие account::session и перенести в mirzaev/ebala/models/worker
*/
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;
}
/**
* Подключить к сессии
*
* Ищет связь сессии с аккаунтом, если не находит, то создаёт её
*
* @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;
}
/**
* Записать
*

@@ -35,7 +35,7 @@ class core extends model
/**
* Путь до файла с настройками подключения к базе данных ArangoDB
*/
final public const ARANGODB = '../settings/arangodb.php';
final public const ARANGODB = __DIR__ . '/../settings/arangodb.php';
/**
* Соединение с базой данных ArangoDB
@@ -203,7 +203,8 @@ class core extends model
*
* @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 {
if (collection::init(static::$arangodb->session, static::COLLECTION)) {
// Инициализирована коллекция

@@ -46,7 +46,7 @@ final class payments extends core
{
try {
// Чтение заявок
$tasks = @task::read("d.date >= $from && d.date <= $to && d.problematic == false && d.completed == true", amount: 999999, return: '{worker: d.worker, market: d.market, date: d.date, work: d.work, start: d.start, end: d.end, commentary: d.commentary, rating: d.rating, review: d.review}', errors: $errors);
$tasks = @task::read("d.date >= $from && d.date <= $to && d.problematic == false && d.completed == true && d.result.processed != true", amount: 999999, return: '{worker: d.worker, market: d.market, date: d.date, work: d.work, start: d.start, end: d.end, commentary: d.commentary, rating: d.rating, review: d.review}', errors: $errors);
if (is_array($tasks) && count($tasks) > 0) {
// Найдены заявки
@@ -208,9 +208,9 @@ final class payments extends core
->setCellValue("M$row", $worker->name['second'] . ' ' . $worker->name['first'] . ' ' . $worker->name['last'])
->setCellValue("N$row", $hour = static::hour('worker', $market->city, $task->work))
->setCellValue("O$row", $payment = $hour * $hours)
->setCellValue("P$row", ($penalty = static::penalty($task->rating ?? null)) === null ? $payment : $penalty)
->setCellValue("P$row", ($penalty = static::penalty($task->rating ?? null)) === null ? -$payment : $penalty)
->setCellValue("Q$row", $bonus = static::bonus($task->rating ?? null))
->setCellValue("R$row", $payment + (($penalty = static::penalty($task->rating ?? null)) === null ? $payment : $penalty) + $bonus)
->setCellValue("R$row", $payment + ($penalty === null ? -$payment : $penalty) + $bonus)
->setCellValue("S$row", '')
->setCellValue("T$row", $worker->payment) // Наличные?
->setCellValue("U$row", '')
@@ -253,7 +253,7 @@ final class payments extends core
/**
* Магазины
*
* Расчитать ... и сгенерировать excel-документ
* Расчитать прибыль с магазинов и сгенерировать excel-документ
*
* @param int $from Начальная дата для выборки заявок (unixtime)
* @param int $to Конечная дата для выборки заявок (unixtime)
@@ -488,7 +488,15 @@ final class payments extends core
// Инкрементация счётчика для генерации следующей строки
++$row;
}
}
} // Чтение заявок
$tasks = @task::collect(
"d.date >= $from && d.date <= $to && d.problematic == false && d.completed == true",
sort: 'd.date DESC',
amount: 999999,
index: 'd.date',
return: '{worker: d.worker, market: d.market, date: d.date, work: d.work, start: d.start, end: d.end, commentary: d.commentary, rating: d.rating, review: d.review}',
errors: $errors
);
// Запись строки с общими данными магазина
$spreadsheet
@@ -575,6 +583,60 @@ final class payments extends core
return false;
}
/**
* Подтвердить обработку
*
* Отметить в базе данных то, что выбранные заявки были обработаны
*
* @param int $from Начальная дата для выборки заявок (unixtime)
* @param int $to Конечная дата для выборки заявок (unixtime)
* @param string $type Тип документа для подтверждения (workers, markets)
* @param array $errors Errors registry
*
* @return void
*/
public static function confirm(int $from, int $to, string $type, array &$errors = []): bool
{
try {
// Чтение заявок
$tasks = @task::read("d.date >= $from && d.date <= $to && d.problematic == false && d.completed == true && d.result.processed != true", amount: 999999, errors: $errors);
if (is_array($tasks) && count($tasks) > 0) {
// Найдены заявки
if ($type === 'workers') {
// Подтверждена обработка зарплат сотрудников за выбранный период
foreach ($tasks as $task) {
// Перебор заявок
// Подтверждение того, что заявка обработана (выплачены деньги сотруднику)
$task->result = ['processed' => true] + ($task->result ?? []);
// Запись обновления в базу данных
core::update($task);
}
}
// Exit (success)
return true;
}
throw new exception('Не найдены заявки');
} catch (exception $e) {
// Write to the errors registry
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
// Exit (fail)
return false;
}
/**
* Determine tariff
@@ -589,72 +651,8 @@ final class payments extends core
{
return
match (mb_strtolower($type)) {
'market', 'магазин' => match (mb_strtolower($city)) {
'красноярск' => match (mb_strtolower($work)) {
'cashiers', 'cashier', 'кассиры', 'кассир' => 257.07,
'displayers', 'displayer', 'выкладчики', 'выкладчик' => 257.07,
'gastronomes', 'gastronome', 'гастрономы', 'гастроном' => 257.07,
'brigadiers', 'brigadier', 'бригадиры', 'бригадир' => 360,
'loaders', 'loader', 'грузчики', 'грузчик' => 255.645,
'loaders_mobile', 'loader_mobile', 'мобильные грузчики', 'мобильный грузчик' => 305,
'universals_mobile', 'universal_mobile', 'мобильные универсалы', 'мобильный универсал' => 305,
default => 0
},
'железногорск', 'сосновоборск', 'тыва' => match (mb_strtolower($work)) {
'cashiers', 'cashier', 'кассиры', 'кассир' => 263.34,
'displayers', 'displayer', 'выкладчики', 'выкладчик' => 263.34,
'gastronomes', 'gastronome', 'гастрономы', 'гастроном' => 263.34,
'brigadiers', 'brigadier', 'бригадиры', 'бригадир' => 360,
'loaders', 'loader', 'грузчики', 'грузчик' => 255.645,
'loaders_mobile', 'loader_mobile', 'мобильные грузчики', 'мобильный грузчик' => 305,
'universals_mobile', 'universal_mobile', 'мобильные универсалы', 'мобильный универсал' => 305,
default => 0
},
'хакасия', 'иркутск' => match (mb_strtolower($work)) {
'cashiers', 'cashier', 'кассиры', 'кассир' => 245.385,
'displayers', 'displayer', 'выкладчики', 'выкладчик' => 245.385,
'gastronomes', 'gastronome', 'гастрономы', 'гастроном' => 245.385,
'brigadiers', 'brigadier', 'бригадиры', 'бригадир' => 360,
'loaders', 'loader', 'грузчики', 'грузчик' => 255.645,
'loaders_mobile', 'loader_mobile', 'мобильные грузчики', 'мобильный грузчик' => 305,
'universals_mobile', 'universal_mobile', 'мобильные универсалы', 'мобильный универсал' => 305,
default => 0
},
default => 0
},
'worker', 'сотрудник' => match (mb_strtolower($city)) {
'красноярск' => match (mb_strtolower($work)) {
'cashiers', 'cashier', 'кассиры', 'кассир' => 190.91,
'displayers', 'displayer', 'выкладчики', 'выкладчик' => 190.91,
'gastronomes', 'gastronome', 'гастрономы', 'гастроном' => 190.91,
'brigadiers', 'brigadier', 'бригадиры', 'бригадир' => 250,
'loaders', 'loader', 'грузчики', 'грузчик' => 177.27,
'loaders_mobile', 'loader_mobile', 'мобильные грузчики', 'мобильный грузчик' => 250,
'universals_mobile', 'universal_mobile', 'мобильные универсалы', 'мобильный универсал' => 250,
default => 0
},
'железногорск', 'сосновоборск', 'тыва' => match (mb_strtolower($work)) {
'cashiers', 'cashier', 'кассиры', 'кассир' => 190.91,
'displayers', 'displayer', 'выкладчики', 'выкладчик' => 190.91,
'gastronomes', 'gastronome', 'гастрономы', 'гастроном' => 190.91,
'brigadiers', 'brigadier', 'бригадиры', 'бригадир' => 250,
'loaders', 'loader', 'грузчики', 'грузчик' => 177.27,
'loaders_mobile', 'loader_mobile', 'мобильные грузчики', 'мобильный грузчик' => 250,
'universals_mobile', 'universal_mobile', 'мобильные универсалы', 'мобильный универсал' => 250,
default => 0
},
'хакасия', 'иркутск' => match (mb_strtolower($work)) {
'cashiers', 'cashier', 'кассиры', 'кассир' => 181.82,
'displayers', 'displayer', 'выкладчики', 'выкладчик' => 181.82,
'gastronomes', 'gastronome', 'гастрономы', 'гастроном' => 181.82,
'brigadiers', 'brigadier', 'бригадиры', 'бригадир' => 250,
'loaders', 'loader', 'грузчики', 'грузчик' => 168.18,
'loaders_mobile', 'loader_mobile', 'мобильные грузчики', 'мобильный грузчик' => 250,
'universals_mobile', 'universal_mobile', 'мобильные универсалы', 'мобильный универсал' => 250,
default => 0
},
default => 0
},
'market', 'магазин' => settings::read("d.category == 'market_hour' && d.city == '$city' && d.work == '$work'")?->value ?? 0,
'worker', 'сотрудник' => settings::read("d.category == 'worker_hour' && d.city == '$city' && d.work == '$work'")?->value ?? 0,
default => 0
};
}
@@ -668,10 +666,7 @@ final class payments extends core
*/
public static function bonus(int $rating): int
{
return match ($rating) {
5 => 100,
default => 0
};
return settings::read("d.category == 'worker_bonus' && d.rating == $rating")?->value ?? 0;
}
/**
@@ -683,11 +678,8 @@ final class payments extends core
*/
public static function penalty(int $rating): ?int
{
return match ($rating) {
3 => -100,
2 => -500,
1 => null,
default => 0
};
$penalty = settings::read("d.category == 'worker_penalty' && d.rating == $rating")?->value ?? 0;
return $penalty === 1 ? null : $penalty;
}
}

@@ -36,7 +36,9 @@ final class registry extends core
* @param ?string $after Injection of AQL-code after search of edges
* @param int $amount Amount of workers
* @param int $page Offset by amount
* @param string $sort Sort
* @param string $target Collection or view name
* @param string $return Data for return
* @param array $binds Binds for query
* @param array $errors Errors registry
*
@@ -49,6 +51,7 @@ final class registry extends core
int $page = 1,
string $sort = 'account.created DESC, account._key DESC',
string $target = account::COLLECTION,
string $return = '{account, worker}',
array $binds = [],
array &$errors = []
): array {
@@ -69,14 +72,15 @@ final class registry extends core
%s
SORT %s
LIMIT %d, %d
RETURN {account, worker}
RETURN %s
AQL,
$target,
$before,
$after,
$sort,
--$page <= 0 ? 0 : $amount * $page,
$amount
$amount,
$return
), $binds);
// Exit (success)
@@ -104,6 +108,7 @@ final class registry extends core
* @param int $amount Amount of markets
* @param int $page Offset by amount
* @param string $target Collection or view name
* @param string $return Data for return
* @param array $binds Binds for query
* @param array $errors Errors registry
*
@@ -116,6 +121,7 @@ final class registry extends core
int $page = 1,
string $sort = 'account.created DESC, account._key DESC',
string $target = account::COLLECTION,
string $return = '{account, market}',
array $binds = [],
array &$errors = []
): array {
@@ -136,14 +142,15 @@ final class registry extends core
%s
SORT %s
LIMIT %d, %d
RETURN {account, market}
RETURN %s
AQL,
$target,
$before,
$after,
$sort,
--$page <= 0 ? 0 : $amount * $page,
$amount
$amount,
$return
), $binds);
// Exit (success)
@@ -167,9 +174,11 @@ final class registry extends core
* Generate operators list
*
* @param ?string $before Injection of AQL-code before search of edges
* @param ?string $after Injection of AQL-code after search of edges
* @param int $amount Amount of operators
* @param int $page Offset by amount
* @param string $target Collection or view name
* @param string $return Data for return
* @param array $binds Binds for query
* @param array $errors Errors registry
*
@@ -177,10 +186,12 @@ final class registry extends core
*/
public static function operators(
?string $before = '',
?string $after = '',
int $amount = 100,
int $page = 1,
string $sort = 'account.created DESC, account._key DESC',
string $target = account::COLLECTION,
string $return = '{account}',
array $binds = [],
array &$errors = []
): array {
@@ -192,16 +203,19 @@ final class registry extends core
$operators = collection::search(static::$arangodb->session, sprintf(
<<<AQL
FOR account IN %s
%s
%s
SORT %s
LIMIT %d, %d
RETURN {account}
RETURN %s
AQL,
$target,
$before,
$after,
$sort,
--$page <= 0 ? 0 : $amount * $page,
$amount
$amount,
$return
), $binds);
// Exit (success)
@@ -225,9 +239,11 @@ final class registry extends core
* Generate administrators list
*
* @param ?string $before Injection of AQL-code before search of edges
* @param ?string $after Injection of AQL-code after search of edges
* @param int $amount Amount of administrators
* @param int $page Offset by amount
* @param string $target Collection or view name
* @param string $return Data for return
* @param array $binds Binds for query
* @param array $errors Errors registry
*
@@ -235,10 +251,12 @@ final class registry extends core
*/
public static function administrators(
?string $before = '',
?string $after = '',
int $amount = 100,
int $page = 1,
string $sort = 'account.created DESC, account._key DESC',
string $target = account::COLLECTION,
string $return = '{account}',
array $binds = [],
array &$errors = []
): array {
@@ -250,16 +268,19 @@ final class registry extends core
$administrators = collection::search(static::$arangodb->session, sprintf(
<<<AQL
FOR account IN %s
%s
%s
SORT %s
LIMIT %d, %d
RETURN {account}
RETURN %s
AQL,
$target,
$before,
$after,
$sort,
--$page <= 0 ? 0 : $amount * $page,
$amount
$amount,
$return
), $binds);
// Exit (success)
@@ -273,7 +294,6 @@ final class registry extends core
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
var_dump($errors);
}
// Exit (fail)

@@ -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 Реестр ошибок
*
* @return ?account Инстанция аккаунта, если удалось найти
* @return account|null Инстанция аккаунта, если удалось найти
*/
public function account(array &$errors = []): ?account
{
@@ -273,6 +218,7 @@ final class session extends core
];
}
// Возврат (провал)
return null;
}

@@ -0,0 +1,111 @@
<?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 settings extends core
{
use instance, status;
/**
* Collection name in ArangoDB
*/
final public const COLLECTION = 'settings';
/**
* Инстанция документа в базе данных
*/
protected readonly _document $document;
/**
* Read (search)
*
* @param ?string $before Injection of AQL-code before search of edges
* @param int $amount Amount of administrators
* @param int $page Offset by amount
* @param string $target Collection or view name
* @param array $binds Binds for query
* @param array $errors Errors registry
*
* @return array Instances from ArangoDB
*/
public static function search(
?string $before = '',
int $amount = 1000,
int $page = 1,
string $sort = 'setting.category ASC, setting.city ASC, setting.work ASC, setting.rating DESC, setting.created DESC, setting._key DESC',
string $target = settings::COLLECTION,
array $binds = [],
array &$errors = []
): array {
try {
if (collection::init(static::$arangodb->session, settings::COLLECTION)) {
// Инициализирована коллекция
// Search the session data in ArangoDB
$settings = collection::search(static::$arangodb->session, sprintf(
<<<AQL
FOR setting IN %s
%s
SORT %s
LIMIT %d, %d
LET d = setting.category
COLLECT x = setting.category INTO groups
RETURN { [x]: groups[*]['setting'] }
AQL,
$target,
$before,
$sort,
--$page <= 0 ? 0 : $amount * $page,
$amount
), $binds);
// Универсализация значений
$buffer = [];
foreach (is_array($settings) ? $settings : [$settings] as $setting) foreach ($setting->getAll() ?? [] as $category => $data) $buffer[$category] = $data;
$settings = $buffer;
// Exit (success)
return $settings;
} else throw new exception('Не удалось инициализировать коллекции');
} catch (exception $e) {
// Write to the errors registry
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
var_dump($errors);
}
// Exit (fail)
return [];
}
}

@@ -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;
}
}

@@ -118,6 +118,7 @@ final class task extends core
* @param int $amount Amount of tasks
* @param int $page Offset by amount
* @param string $target Collection or view name
* @param string $return Data for return
* @param array $binds Binds for query
* @param array $errors Errors registry
*
@@ -133,6 +134,7 @@ final class task extends core
int $page = 1,
string $sort = 'task.date DESC, task.created DESC, task._key DESC',
string $target = self::COLLECTION,
string $return = '{task, worker, market}',
array $binds = [],
array &$errors = []
): array {
@@ -154,7 +156,7 @@ final class task extends core
%s
SORT %s
LIMIT %d, %d
RETURN {task, worker, market}
RETURN %s
AQL,
$target,
$before,
@@ -163,7 +165,8 @@ final class task extends core
$after,
$sort,
--$page <= 0 ? 0 : $amount * $page,
$amount
$amount,
$return
), $binds);
// Exit (success)
@@ -259,7 +262,7 @@ final class task extends core
public static function transaction(
string $task,
string $worker,
int $amount = 0,
int|float $amount = 0,
array &$errors = []
): ?string {
try {
@@ -292,4 +295,99 @@ final class task extends core
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;
}
}

@@ -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

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

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

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

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

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

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

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

@@ -44,6 +44,12 @@ div#popup>section.list>div.row.endless {
height: auto;
}
section.panel.list> :is(form, search).row.menu>label>div:has(>button) {
position: relative;
display: flex;
height: 30px;
}
section.panel.list> :is(form, search).row.menu>label>button {
position: relative;
display: flex;
@@ -106,13 +112,14 @@ section.panel.list>div#title>span {
}
section.panel.list>div.row {
--width: calc(100% - 24px);
--gap: 12px;
--background: var(--cloud);
position: relative;
left: 0px;
width: calc(100% - 24px);
height: 35px;
display: flex;
width: var(--width, calc(100% - 24px));
gap: var(--gap, 12px);
padding: 0 var(--gap, 12px);
border-radius: 0px;
@@ -132,7 +139,7 @@ section.panel.list>div.row:not(:nth-of-type(1))>span {
-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-right: 24px;
left: -12px;
@@ -275,7 +282,7 @@ section.panel.list>div.row>span.field {
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);
--border-left: calc(var(--padding-left, var(--margin, 0px)) * -1);
--border-right: var(--padding-right, var(--margin, 0px));
@@ -476,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)) {
--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;
}

@@ -58,7 +58,8 @@ input[type="range"] {
button,
input[type="submit"],
input[type="range"] {
input[type="range"],
input[type="checkbox"] {
cursor: pointer;
}

@@ -59,3 +59,19 @@ section#operators.panel.list > div.row > span[data-column="commentary"] {
min-width: unset;
width: 100%;
}
section#operators.panel.list>div.row[data-row="operator"]:not(:nth-of-type(1)).transactions {
--background: var(--magma);
}
section#operators.panel.list>div.row[data-row="operator"]:not(:nth-of-type(1)):nth-child(2n + 1).transactions {
--background: var(--magma-above);
}
section#operators.panel.list>div.row[data-row="operator"]:not(:nth-of-type(1)).transactions>span:is(.important, .interactive:is(:hover, :focus)) {
--background: var(--magma-important);
}
section#operators.panel.list>div.row[data-row="operator"]:not(:nth-of-type(1)):nth-child(2n + 1).transactions>span:is(.important, .interactive:is(:hover, :focus)) {
--background: var(--magma-important-above);
}

@@ -0,0 +1,73 @@
@charset "UTF-8";
main>section#settings.panel {
z-index: 1000;
width: 800px;
position: relative;
margin-bottom: calc(15vh - 45px);
display: flex;
flex-direction: column;
gap: 60px;
border-radius: 3px;
}
main>section#settings.panel>section.category {
display: flex;
flex-direction: column;
gap: 20px;
padding: 30px 40px;
border-radius: 3px;
background-color: var(--snow);
}
main>section#settings.panel>section.category>div {
padding-bottom: 6px;
display: flex;
flex-direction: column;
}
main>section#settings.panel>section.category>div>div {
margin: 0;
margin-left: 30px;
margin-bottom: 6px;
display: inline-flex;
align-items: center;
}
main>section#settings.panel>section.category>div>div>h2 {
margin: 0;
}
main>section#settings.panel>section.category>div>div>i {
position: relative;
margin-left: auto;
margin-right: 20px;
}
main>section#settings.panel>section.category>div>small {
margin-left: 20px;
width: 100%;
}
main>section#settings.panel>section.category>section.subcategory {
padding: 20px 20px;
display: flex;
flex-direction: column;
gap: 8px;
border-radius: 3px;
background-color: var(--snow-deep);
}
main>section#settings.panel>section.category>section.subcategory>h3 {
margin: 6px 0px 3px 25px;
}
main>section#settings.panel>section.category>section.subcategory>label>input {
width: 90px;
}
main>section#settings.panel>section.category>section.subcategory>p.empty {
text-align: center;
padding: 0 20%;
font-weight: bold;
}

21
mirzaev/ebala/system/public/css/popup.css Normal file → Executable file

@@ -141,6 +141,25 @@ div#popup>section.list>section.main>div.column>:is(div, section).row>label>input
text-align: center;
}
div#popup>section.list>section.main>div.column> :is(div, select).row:has(>label>select[multiple]) {
height: auto;
max-height: 60px;
}
div#popup>section.list>section.main>div.column> :is(div, select).row>label>select[multiple] {
height: auto;
padding: 0px;
border-radius: 3px;
}
div#popup>section.list>section.main>div.column> :is(div, select).row>label>select[multiple]>option {
padding: 5px 10px;
}
/* div#popup>section.list>section.main>div.column> :is(div, select).row>label>select[multiple]>option[selected] {
background-color: var(--background-above-5);
} */
div#popup>section.list>section.main>div.column>:is(div, section).row>label> :is(input, button):only-child {
width: 100%;
}
@@ -297,7 +316,7 @@ div#popup>section.list.errors>section.body>dl>dd {
margin-left: 20px;
}
div#popup > section.list .separator {
div#popup>section.list .separator {
border-top: 2px solid var(--separator, var(--cloud));
padding-top: 10px;
margin-top: 10px;

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

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