diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..1dda020 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +!.gitignore +composer.phar +vendor diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/composer.json b/composer.json new file mode 100755 index 0000000..c147da7 --- /dev/null +++ b/composer.json @@ -0,0 +1,57 @@ +{ + "name": "mirzaev/ebala", + "description": "PHP CRM based on ArangoDB", + "readme": "README.md", + "keywords": [ + "site", + "crm", + "arangodb" + ], + "type": "project", + "homepage": "https://git.mirzaev.sexy/mirzaev/ebala", + "license": "WTFPL", + "authors": [ + { + "name": "Arsen Mirzaev Tatyano-Muradovich", + "email": "arsen@mirzaev.sexy", + "homepage": "https://mirzaev.sexy", + "role": "Programmer" + } + ], + "support": { + "email": "arsen@mirzaev.sexy", + "wiki": "https://git.mirzaev.sexy/mirzaev/ebala/wiki", + "issues": "https://git.mirzaev.sexy/mirzaev/ebala/issues" + }, + "funding": [ + { + "type": "funding", + "url": "https://fund.mirzaev.sexy" + } + ], + "require": { + "php": "~8.2", + "ext-sodium": "~8.2.4", + "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/extra-bundle": "^3.7", + "twig/intl-extra": "^3.7", + "phpoffice/phpspreadsheet": "^1.29" + }, + "require-dev": { + "phpunit/phpunit": "~9.5" + }, + "autoload": { + "psr-4": { + "mirzaev\\ebala\\": "mirzaev/ebala/system" + } + }, + "autoload-dev": { + "psr-4": { + "mirzaev\\ebala\\tests\\": "mirzaev/ebala/tests" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100755 index 0000000..01f84c0 --- /dev/null +++ b/composer.lock @@ -0,0 +1,5401 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "588f1020cbf90d4c8ed02b057592c14f", + "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", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/fb7566caccf22d74d1ab270de3551f72a58399f5", + "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "ext-curl": "*", + "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.7.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2023-05-21T14:04:53+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/3a494dc7dc1d7d12e511890177ae2d0e6c107da6", + "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2023-05-21T13:50:22+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.5.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "b635f279edd83fc275f822a1188157ffea568ff6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6", + "reference": "b635f279edd83fc275f822a1188157ffea568ff6", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.5.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2023-04-17T16:11:26+00:00" + }, + { + "name": "maennchen/zipstream-php", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "b8174494eda667f7d13876b4a7bfef0f62a7c0d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/b8174494eda667f7d13876b4a7bfef0f62a7c0d1", + "reference": "b8174494eda667f7d13876b4a7bfef0f62a7c0d1", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-zlib": "*", + "php-64bit": "^8.1" + }, + "require-dev": { + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.16", + "guzzlehttp/guzzle": "^7.5", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^10.0", + "vimeo/psalm": "^5.0" + }, + "suggest": { + "guzzlehttp/psr7": "^2.4", + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.0" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + }, + { + "url": "https://opencollective.com/zipstream", + "type": "open_collective" + } + ], + "time": "2023-06-21T14:59:35+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, + { + "name": "mirzaev/accounts", + "version": "1.2.x-dev", + "source": { + "type": "git", + "url": "https://git.mirzaev.sexy/mirzaev/accounts", + "reference": "aa93c4d26395025fa16bb65e8a40332ac352a742" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "guzzlehttp/guzzle": "^7.5", + "php": "~8.2" + }, + "require-dev": { + "phpdocumentor/phpdocumentor": ">=2.9", + "phpunit/phpunit": "^9" + }, + "type": "library", + "autoload": { + "psr-4": { + "mirzaev\\accounts\\": "mirzaev/accounts/system" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Arsen Mirzaev Tatyano-Muradovich", + "email": "arsen@mirzaev.sexy", + "homepage": "https://mirzaev.sexy", + "role": "Programmer" + } + ], + "description": "Simple accounts manager", + "homepage": "https://git.mirzaev.sexy/mirzaev/accounts", + "keywords": [ + "accounts" + ], + "support": { + "email": "arsen@mirzaev.sexy", + "issues": "https://git.mirzaev.sexy/mirzaev/accounts/issues", + "wiki": "https://git.mirzaev.sexy/mirzaev/accounts/wiki" + }, + "funding": [ + { + "url": "https://fund.mirzaev.sexy", + "type": "funding" + } + ], + "time": "2023-02-17T08:36:36+00:00" + }, + { + "name": "mirzaev/arangodb", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://git.mirzaev.sexy/mirzaev/arangodb", + "reference": "11be80b0ad9bd401e0c9238c83a1459f7a992e06" + }, + "require": { + "php": "^8.2", + "triagens/arangodb": "~3.8" + }, + "require-dev": { + "phpunit/phpunit": "^9.3.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "mirzaev\\arangodb\\": "mirzaev/arangodb/system" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Arsen Mirzaev Tatyano-Muradovich", + "email": "arsen@mirzaev.sexy", + "homepage": "https://mirzaev.sexy", + "role": "Developer" + } + ], + "description": "Simple PHP-framework for ArangoDB", + "homepage": "https://git.hood.su/mirzaev/arangodb", + "keywords": [ + "ArangoDb" + ], + "support": { + "email": "arsen@mirzaev.sexy", + "issues": "https://git.mirzaev.sexy/mirzaev/arangodb/issues", + "wiki": "https://git.mirzaev.sexy/mirzaev/arangodb/manual" + }, + "funding": [ + { + "url": "https://fund.mirzaev.sexy", + "type": "funding" + } + ], + "time": "2023-07-09T16:45:49+00:00" + }, + { + "name": "mirzaev/minimal", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://git.mirzaev.sexy/mirzaev/minimal", + "reference": "3d31c92628579a0b38c7a84f6a0e9c2f67c1cf54" + }, + "require": { + "php": "~8.2" + }, + "type": "framework", + "autoload": { + "psr-4": { + "mirzaev\\minimal\\": "mirzaev/minimal/system" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Arsen Mirzaev Tatyano-Muradovich", + "email": "arsen@mirzaev.sexy", + "homepage": "https://mirzaev.sexy", + "role": "Developer" + } + ], + "description": "Lightweight MVC framework that manages only the basic mechanisms, leaving the development of the programmer and not overloading the project", + "homepage": "https://git.mirzaev.sexy/mirzaev/minimal", + "keywords": [ + "framework", + "lightweight", + "mvc" + ], + "support": { + "docs": "https://git.mirzaev.sexy/mirzaev/minimal/wiki", + "issues": "https://git.mirzaev.sexy/mirzaev/minimal/issues" + }, + "time": "2023-03-20T11:46:41+00:00" + }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.29.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fde2ccf55eaef7e86021ff1acce26479160a0fa0", + "reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "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", + "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", + "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", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.0" + }, + "time": "2023-06-14T22:48:31+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/1.0.2" + }, + "time": "2023-04-10T20:12:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "e616d01114759c4c489f93b099585439f795fe35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + }, + "time": "2023-04-10T20:10:41+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "symfony/cache", + "version": "v6.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "e60d00b4f633efa4c1ef54e77c12762d9073e7b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/e60d00b4f633efa4c1ef54e77c12762d9073e7b3", + "reference": "e60d00b4f633efa4c1ef54e77c12762d9073e7b3", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.2.10" + }, + "conflict": { + "doctrine/dbal": "<2.13.1", + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/var-dumper": "<5.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v6.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-05T09:10:27+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "ad945640ccc0ae6e208bcea7d7de4b39b569896b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/ad945640ccc0ae6e208bcea7d7de4b39b569896b", + "reference": "ad945640ccc0ae6e208bcea7d7de4b39b569896b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/config", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "b47ca238b03e7b0d7880ffd1cf06e8d637ca1467" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/b47ca238b03e7b0d7880ffd1cf06e8d637ca1467", + "reference": "b47ca238b03e7b0d7880ffd1cf06e8d637ca1467", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^5.4|^6.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "require-dev": { + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-19T20:22:16+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v6.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "68a5a9570806a087982f383f6109c5e925892a49" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/68a5a9570806a087982f383f6109c5e925892a49", + "reference": "68a5a9570806a087982f383f6109c5e925892a49", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.2.10" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.1", + "symfony/finder": "<5.4", + "symfony/proxy-manager-bridge": "<6.3", + "symfony/yaml": "<5.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.1", + "symfony/expression-language": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v6.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-16T17:55:17+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "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" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "85fd65ed295c4078367c784e8a5a6cee30348b7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/85fd65ed295c4078367c784e8a5a6cee30348b7a", + "reference": "85fd65ed295c4078367c784e8a5a6cee30348b7a", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^5.4|^6.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-16T17:05:46+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/adb01fe097a4ee930db9258a3cc906b5beb5cf2e", + "reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-06T06:56:43+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v6.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/edd36776956f2a6fcf577edb5b05eb0e3bdc52ae", + "reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v6.3.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-06-01T08:30:39+00:00" + }, + { + "name": "symfony/finder", + "version": "v6.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "9915db259f67d21eefee768c1abcf1cc61b1fc9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/9915db259f67d21eefee768c1abcf1cc61b1fc9e", + "reference": "9915db259f67d21eefee768c1abcf1cc61b1fc9e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v6.3.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-31T08:31:44+00:00" + }, + { + "name": "symfony/framework-bundle", + "version": "v6.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/framework-bundle.git", + "reference": "f822f54ff05cd88878910b4559f66c12176d952c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/f822f54ff05cd88878910b4559f66c12176d952c", + "reference": "f822f54ff05cd88878910b4559f66c12176d952c", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "php": ">=8.1", + "symfony/cache": "^5.4|^6.0", + "symfony/config": "^6.1", + "symfony/dependency-injection": "^6.3.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.1", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-foundation": "^6.3", + "symfony/http-kernel": "^6.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/routing": "^5.4|^6.0" + }, + "conflict": { + "doctrine/annotations": "<1.13.1", + "doctrine/persistence": "<1.3", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/asset": "<5.4", + "symfony/clock": "<6.3", + "symfony/console": "<5.4", + "symfony/dom-crawler": "<6.3", + "symfony/dotenv": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<6.3", + "symfony/lock": "<5.4", + "symfony/mailer": "<5.4", + "symfony/messenger": "<6.3", + "symfony/mime": "<6.2", + "symfony/property-access": "<5.4", + "symfony/property-info": "<5.4", + "symfony/security-core": "<5.4", + "symfony/security-csrf": "<5.4", + "symfony/serializer": "<6.3", + "symfony/stopwatch": "<5.4", + "symfony/translation": "<6.2.8", + "symfony/twig-bridge": "<5.4", + "symfony/twig-bundle": "<5.4", + "symfony/validator": "<6.3", + "symfony/web-profiler-bundle": "<5.4", + "symfony/workflow": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.13.1|^2", + "doctrine/persistence": "^1.3|^2|^3", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^5.4|^6.0", + "symfony/asset-mapper": "^6.3", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/clock": "^6.2", + "symfony/console": "^5.4.9|^6.0.9", + "symfony/css-selector": "^5.4|^6.0", + "symfony/dom-crawler": "^6.3", + "symfony/dotenv": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/html-sanitizer": "^6.1", + "symfony/http-client": "^6.3", + "symfony/lock": "^5.4|^6.0", + "symfony/mailer": "^5.4|^6.0", + "symfony/messenger": "^6.3", + "symfony/mime": "^6.2", + "symfony/notifier": "^5.4|^6.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/process": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/rate-limiter": "^5.4|^6.0", + "symfony/scheduler": "^6.3", + "symfony/security-bundle": "^5.4|^6.0", + "symfony/semaphore": "^5.4|^6.0", + "symfony/serializer": "^6.3", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/string": "^5.4|^6.0", + "symfony/translation": "^6.2.8", + "symfony/twig-bundle": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^6.3", + "symfony/web-link": "^5.4|^6.0", + "symfony/workflow": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0", + "twig/twig": "^2.10|^3.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\FrameworkBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/framework-bundle/tree/v6.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-16T18:04:38+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v6.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "cac1556fdfdf6719668181974104e6fcfa60e844" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/cac1556fdfdf6719668181974104e6fcfa60e844", + "reference": "cac1556fdfdf6719668181974104e6fcfa60e844", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "symfony/cache": "<6.2" + }, + "require-dev": { + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", + "symfony/mime": "^5.4|^6.0", + "symfony/rate-limiter": "^5.2|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v6.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-22T08:20:46+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v6.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "36abb425b4af863ae1fe54d8a8b8b4c76a2bccdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/36abb425b4af863ae1fe54d8a8b8b4c76a2bccdb", + "reference": "36abb425b4af863ae1fe54d8a8b8b4c76a2bccdb", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.3", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/http-foundation": "^6.3.4", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<5.4", + "symfony/cache": "<5.4", + "symfony/config": "<6.1", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<6.3.4", + "symfony/doctrine-bridge": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<5.4", + "symfony/messenger": "<5.4", + "symfony/translation": "<5.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<5.4", + "symfony/validator": "<5.4", + "symfony/var-dumper": "<6.3", + "twig/twig": "<2.13" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/clock": "^6.2", + "symfony/config": "^6.1", + "symfony/console": "^5.4|^6.0", + "symfony/css-selector": "^5.4|^6.0", + "symfony/dependency-injection": "^6.3.4", + "symfony/dom-crawler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^5.4|^6.0", + "symfony/property-access": "^5.4.5|^6.0.5", + "symfony/routing": "^5.4|^6.0", + "symfony/serializer": "^6.3", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^6.3", + "symfony/var-exporter": "^6.2", + "twig/twig": "^2.13|^3.0.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v6.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-26T13:54:49+00:00" + }, + { + "name": "symfony/intl", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/intl.git", + "reference": "1f8cb145c869ed089a8531c51a6a4b31ed0b3c69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/intl/zipball/1f8cb145c869ed089a8531c51a6a4b31ed0b3c69", + "reference": "1f8cb145c869ed089a8531c51a6a4b31ed0b3c69", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/var-exporter": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Intl\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Eriksen Costa", + "email": "eriksen.costa@infranology.com.br" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides access to the localization data of the ICU library", + "homepage": "https://symfony.com", + "keywords": [ + "i18n", + "icu", + "internationalization", + "intl", + "l10n", + "localization" + ], + "support": { + "source": "https://github.com/symfony/intl/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-20T07:43:09+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", + "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-php80": "^1.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-16T06:22:46+00:00" + }, + { + "name": "symfony/routing", + "version": "v6.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "e7243039ab663822ff134fbc46099b5fdfa16f6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/e7243039ab663822ff134fbc46099b5fdfa16f6a", + "reference": "e7243039ab663822ff134fbc46099b5fdfa16f6a", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/annotations": "<1.12", + "symfony/config": "<6.2", + "symfony/dependency-injection": "<5.4", + "symfony/yaml": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.12|^2", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.2", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v6.3.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-31T07:08:24+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "02c24deb352fb0d79db5486c0c79905a85e37e86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/02c24deb352fb0d79db5486c0c79905a85e37e86", + "reference": "02c24deb352fb0d79db5486c0c79905a85e37e86", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-30T17:17:10+00:00" + }, + { + "name": "symfony/twig-bridge", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "6f8435db76a2d79917489a19a82679276c1b4e32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/6f8435db76a2d79917489a19a82679276c1b4e32", + "reference": "6f8435db76a2d79917489a19a82679276c1b4e32", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/translation-contracts": "^2.5|^3", + "twig/twig": "^2.13|^3.0.4" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/console": "<5.4", + "symfony/form": "<6.3", + "symfony/http-foundation": "<5.4", + "symfony/http-kernel": "<6.2", + "symfony/mime": "<6.2", + "symfony/translation": "<5.4", + "symfony/workflow": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.12|^2", + "egulias/email-validator": "^2.1.10|^3|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^5.4|^6.0", + "symfony/asset-mapper": "^6.3", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/form": "^6.3", + "symfony/html-sanitizer": "^6.1", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^6.2", + "symfony/intl": "^5.4|^6.0", + "symfony/mime": "^6.2", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/routing": "^5.4|^6.0", + "symfony/security-acl": "^2.8|^3.0", + "symfony/security-core": "^5.4|^6.0", + "symfony/security-csrf": "^5.4|^6.0", + "symfony/security-http": "^5.4|^6.0", + "symfony/serializer": "^6.2", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/translation": "^6.1", + "symfony/web-link": "^5.4|^6.0", + "symfony/workflow": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0", + "twig/cssinliner-extra": "^2.12|^3", + "twig/inky-extra": "^2.12|^3", + "twig/markdown-extra": "^2.12|^3" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Twig\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides integration for Twig with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/twig-bridge/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-20T16:42:33+00:00" + }, + { + "name": "symfony/twig-bundle", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bundle.git", + "reference": "d0cd4d1675c0582d27c2e8bb0dc27c0303d8e3ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/d0cd4d1675c0582d27c2e8bb0dc27c0303d8e3ea", + "reference": "d0cd4d1675c0582d27c2e8bb0dc27c0303d8e3ea", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "php": ">=8.1", + "symfony/config": "^6.1", + "symfony/dependency-injection": "^6.1", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^6.2", + "symfony/twig-bridge": "^6.3", + "twig/twig": "^2.13|^3.0.4" + }, + "conflict": { + "symfony/framework-bundle": "<5.4", + "symfony/translation": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.10.4|^2", + "symfony/asset": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/framework-bundle": "^5.4|^6.0", + "symfony/routing": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/web-link": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\TwigBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a tight integration of Twig into the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/twig-bundle/tree/v6.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-06T09:53:41+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "2027be14f8ae8eae999ceadebcda5b4909b81d45" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/2027be14f8ae8eae999ceadebcda5b4909b81d45", + "reference": "2027be14f8ae8eae999ceadebcda5b4909b81d45", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-24T14:51:05+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v6.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "df1f8aac5751871b83d30bf3e2c355770f8f0691" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/df1f8aac5751871b83d30bf3e2c355770f8f0691", + "reference": "df1f8aac5751871b83d30bf3e2c355770f8f0691", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/var-dumper": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v6.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-16T18:14:47+00:00" + }, + { + "name": "triagens/arangodb", + "version": "3.9.x-dev", + "source": { + "type": "git", + "url": "https://github.com/arangodb/arangodb-php.git", + "reference": "21c460dbbd75eb5c066f9abcc3a9adf4487d6e48" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/arangodb/arangodb-php/zipball/21c460dbbd75eb5c066f9abcc3a9adf4487d6e48", + "reference": "21c460dbbd75eb5c066f9abcc3a9adf4487d6e48", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "ArangoDBClient": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Jan Steemann", + "homepage": "https://github.com/arangodb/arangodb-php", + "role": "Developer" + }, + { + "name": "Frank Mayer", + "homepage": "https://github.com/arangodb/arangodb-php", + "role": "Developer" + }, + { + "name": "Contributors", + "homepage": "https://github.com/arangodb/arangodb-php/graphs/contributors" + } + ], + "description": "ArangoDB PHP client", + "homepage": "https://github.com/arangodb/arangodb-php", + "keywords": [ + "Arango", + "ArangoDb", + "database", + "distributed", + "document store", + "graph database", + "multi-model", + "nosql" + ], + "support": { + "issues": "https://github.com/arangodb/arangodb-php/issues", + "source": "https://github.com/arangodb/arangodb-php/tree/3.9" + }, + "time": "2022-10-21T15:30:53+00:00" + }, + { + "name": "twig/extra-bundle", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/twigphp/twig-extra-bundle.git", + "reference": "f10baafe6eb0ecd615d52d5cbfb713a39f68e8f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/f10baafe6eb0ecd615d52d5cbfb713a39f68e8f3", + "reference": "f10baafe6eb0ecd615d52d5cbfb713a39f68e8f3", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/framework-bundle": "^5.4|^6.0", + "symfony/twig-bundle": "^5.4|^6.0", + "twig/twig": "^2.7|^3.0" + }, + "require-dev": { + "league/commonmark": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4|^6.3", + "twig/cache-extra": "^3.0", + "twig/cssinliner-extra": "^2.12|^3.0", + "twig/html-extra": "^2.12|^3.0", + "twig/inky-extra": "^2.12|^3.0", + "twig/intl-extra": "^2.12|^3.0", + "twig/markdown-extra": "^2.12|^3.0", + "twig/string-extra": "^2.12|^3.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Twig\\Extra\\TwigExtraBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Symfony bundle for extra Twig extensions", + "homepage": "https://twig.symfony.com", + "keywords": [ + "bundle", + "extra", + "twig" + ], + "support": { + "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2023-07-29T15:34:56+00:00" + }, + { + "name": "twig/intl-extra", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/twigphp/intl-extra.git", + "reference": "4f4fe572f635534649cc069e1dafe4a8ad63774d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/4f4fe572f635534649cc069e1dafe4a8ad63774d", + "reference": "4f4fe572f635534649cc069e1dafe4a8ad63774d", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/intl": "^5.4|^6.0", + "twig/twig": "^2.7|^3.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5.4|^6.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Twig\\Extra\\Intl\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Twig extension for Intl", + "homepage": "https://twig.symfony.com", + "keywords": [ + "intl", + "twig" + ], + "support": { + "source": "https://github.com/twigphp/intl-extra/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2023-07-29T15:34:56+00:00" + }, + { + "name": "twig/twig", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd", + "reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2023-06-08T12:52:13+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:23:10+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2023-03-08T13:26:56+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.16.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "19526a33fb561ef417e822e85f08a00db4059c17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17", + "reference": "19526a33fb561ef417e822e85f08a00db4059c17", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0" + }, + "time": "2023-06-25T14:52:30+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.26", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.15", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-03-06T12:58:08+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "a9aceaf20a682aeacf28d582654a1670d8826778" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a9aceaf20a682aeacf28d582654a1670d8826778", + "reference": "a9aceaf20a682aeacf28d582654a1670d8826778", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.9" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2023-06-11T06:13:56+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:41:17+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-05-07T05:35:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T06:03:37+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-02-14T08:28:10+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:07:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "mirzaev/minimal": 20, + "mirzaev/accounts": 20, + "triagens/arangodb": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "~8.2", + "ext-sodium": "~8.2.4" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/mirzaev/ebala/system/controllers/core.php b/mirzaev/ebala/system/controllers/core.php new file mode 100755 index 0000000..a8f4695 --- /dev/null +++ b/mirzaev/ebala/system/controllers/core.php @@ -0,0 +1,194 @@ + + */ +class core extends controller +{ + /** + * Постфикс + */ + final public const POSTFIX = ''; + + /** + * Инстанция сессии + */ + protected readonly session $session; + + /** + * Инстанция аккаунта + */ + protected readonly ?account $account; + + /** + * Инстанция магазина + */ + protected readonly ?market $market; + + /** + * Реестр ошибок + */ + protected array $errors = [ + 'session' => [], + 'account' => [] + ]; + + /** + * Конструктор + * + * @param bool $initialize Инициализировать контроллер? + */ + public function __construct(bool $initialize = true) + { + // Блокировка запросов от CloudFlare + if ($_SERVER['HTTP_USER_AGENT'] === 'nginx-ssl early hints') return; + + parent::__construct($initialize); + + if ($initialize) { + // Запрошена инициализация + + // Инициализация ядра моделей (соединение с базой данных...) + new model(); + + // Инициализация даты до которой будет активна сессия + $expires = strtotime('+1 week'); + + // Инициализация значения по умолчанию + $_COOKIE["session"] ??= null; + + // Инициализация сессии + $this->session = new session($_COOKIE["session"], $expires); + + if ($_COOKIE["session"] !== ($this->session->hash)) { + // Изменился хеш сессии (подразумевается, что сессия устарела) + + // Запись хеша новой сессии + setcookie( + 'session', + $this->session->hash, + [ + 'expires' => $expires, + 'path' => '/', + 'secure' => true, + 'httponly' => true, + 'samesite' => 'strict' + ] + ); + } + + // Инициализация аккаунта + $this->account = new account($this->session); + + if ($this->account->status()) { + // Инициализирован аккаунт + + // Инициализация магазина + if ($this->account->type === 'market') $this->market = new market(account::market($this->account->getId())); + + if ($this->account->type !== $_SERVER['INTERFACE']) { + // Не соответствие типа аккаунта к запрошенному интерфейсу (например, если оператор зашел на интерфейс магазина) + + // Переадресация + header( + 'Location: ' + . $_SERVER['SCHEME'] + . '://' + . match ($this->account->type) { + 'worker' => 'панель', + 'operator' => 'оператор', + 'market' => 'магазин', + 'administrator' => 'администратор', + default => 'панель' + } + . '.' + . $_SERVER['DOMAIN'] + . $_SERVER['REQUEST_URI'], + true, + 303 + ); + + // Выход (успех) + return; + } + } + + // Инициализация шаблонизатора представлений + $this->view = new templater($this->session, $this->account); + } + } + + protected function authorization(): string + { + if ($_SERVER['INTERFACE'] === 'operator') { + // Оператор + + // Инициализация данных аккаунтов для генерации представления + $this->view->accounts = account::read('d.type == "operator" && d.status == "active"', 'd.name.first DESC', 100, 1, errors: $this->errors['account']); + + // Преобразование в массив, если вернуло инстанцию одного документа, вместо массива инстанций документов + if (!is_array($this->view->accounts)) $this->view->accounts = [$this->view->accounts]; + } else if ($_SERVER['INTERFACE'] === 'market') { + // Магазин + + // Инициализация данных аккаунтов для генерации представления + $this->view->accounts = account::read('d.type == "market" && d.status == "active"', 'd.name.first DESC', 100, 1, $this->errors['account']); + + // Преобразование в массив, если вернуло инстанцию одного документа, вместо массива инстанций документов + if (!is_array($this->view->accounts)) $this->view->accounts = [$this->view->accounts]; + + // Инициализация буфера оболочки аккаунта и магазина + $buffer = []; + + // Инициализация данных магазина для аккаунта для генерации представления + foreach ($this->view->accounts as $vendor) $buffer[] = ['vendor' => $vendor, 'market' => account::market($vendor->getId(), $this->errors['account'])]; + + // Запись в глобальную переменную из буфера + $this->view->accounts = $buffer; + } else if ($_SERVER['INTERFACE'] === 'administrator') { + // Администратор + + // Инициализация данных аккаунтов для генерации представления + $this->view->accounts = account::read('d.type == "administrator" && d.status == "active"', 'd.name.first DESC', 100, 1, errors: $this->errors['account']); + + // Преобразование в массив, если вернуло инстанцию одного документа, вместо массива инстанций документов + if (!is_array($this->view->accounts)) $this->view->accounts = [$this->view->accounts]; + } + + return $this->view->render(DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'entry' . DIRECTORY_SEPARATOR . $_SERVER['INTERFACE'] . '.html'); + } + + /** + * Проверить инициализированность + * + * Проверяет инициализированность свойства в инстанции документа аккаунта из базы данных + * + * @param string $name Название + * + * @return bool Свойство инициализировано? + */ + public function __isset(string $name): bool + { + return match ($name) { + 'account' => isset($this->account->document), + default => isset($this->{$name}) + }; + } +} diff --git a/mirzaev/ebala/system/controllers/index.php b/mirzaev/ebala/system/controllers/index.php new file mode 100755 index 0000000..28e7154 --- /dev/null +++ b/mirzaev/ebala/system/controllers/index.php @@ -0,0 +1,83 @@ + + */ +final class index extends core +{ + /** + * Главная страница + * + * @param array $parameters Параметры запроса + */ + public function index(array $parameters = []): ?string + { + if ($this->account->status()) { + // Авторизован аккаунт + + foreach (['from', 'to'] as $name) { + // Перебор фильтров временного промежутка + + // Инициализация значения (приоритет у cookie) + if (empty($value = (int) ($_COOKIE["tasks_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters'][$name] ?? (($name === 'from') ? time() : strtotime('+1 month'))))) continue; + + // Генерация значения для аттрибута "value" для HTML-элемента + $this->view->{$name} = (int) $value; + } + + foreach (['confirmed', 'waiting', 'published', 'unpublished', 'problematic', 'hided', 'completed'] as $name) { + // Перебор фильтров статусов + + // Инициализация значения (приоритет у cookie) + $value = $_COOKIE["tasks_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters'][$name] ?? null; + + // Найдено значение? + if ($value === null) continue; + + // Генерация класса для HTML-элемента по его состоянию (0 - ОТСУТСТВУЕТ, 1 - И, 2 - ИЛИ) + $this->view->{$name} = match ($value) { + '0', 0 => 'earth', + '1', 1 => 'sand', + '2', 2 => 'river', + default => 'earth' + }; + } + + // Генерация представления + $main = $this->view->render(DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'tasks.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; + } + + /** + * Main menu + * + * @param array $parameters Параметры запроса + */ + public function menu(array $parameters = []): ?string + { + if ($this->account->status()) { + // Авторизован аккаунт + + // Генерация представления + return $this->view->render(DIRECTORY_SEPARATOR . 'menu.html'); + } + } +} diff --git a/mirzaev/ebala/system/controllers/market.php b/mirzaev/ebala/system/controllers/market.php new file mode 100755 index 0000000..6716468 --- /dev/null +++ b/mirzaev/ebala/system/controllers/market.php @@ -0,0 +1,181 @@ + + */ +final class market extends core +{ + use errors; + + /** + * Главная страница + * + * @param array $parameters Параметры запроса + */ + public function index(array $parameters = []): ?string + { + // Авторизация + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) { + // Авторизован аккаунт оператора или администратора + + foreach (['confirmed', 'waiting', 'published', 'unpublished', 'problematic', 'hided', 'completed'] as $name) { + // Перебор фильтров статусов + + // Инициализация значения (приоритет у cookie) + $value = $_COOKIE["markets_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['markets']['filters'][$name] ?? 0; + + // Инициализировано значение? + if ($value === null || $value === 0) continue; + + // Генерация класса для HTML-элемента по его состоянию (0 - ОТСУТСТВУЕТ, 1 - И, 2 - ИЛИ) + $this->view->{$name} = match ($value) { + '0', 0 => 'earth', + '1', 1 => 'sand', + '2', 2 => 'river', + default => 'earth' + }; + } + + // Генерация представлениямя + $main = $this->view->render(DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'markets.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; + } + + /** + * Прочитать + * + * @param array $parameters Параметры запроса + */ + public function read(array $parameters = []): ?string + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) { + // Авторизован аккаунт оператора или администратора + + // Реинициализация актуальной страницы + if (isset($parameters['page'])) $this->session->write(['markets' => ['page' => $parameters['page']]]); + else if (empty($this->session->buffer[$_SERVER['INTERFACE']]['markets']['page'])) $this->session->write(['markets' => ['page' => 1]]); + + // Инициализация буфера AQL-выражения для инъекции фильтра по интервалу + $polysemantic = ''; + + // Инициализация допустимых статусов + $statuses = ['active', 'inactive', 'fined', 'decent', 'hided', 'fired']; + + // Инициализация буфера AQL-выражения для инъекции фильтра по статусам (И) + $statuses_and = ''; + + foreach ($statuses as $name) { + // Перебор фильтров статусов (И) + + // Инициализация значения (приоритет у cookie) (отсутствие значения или значение 0 вызывают continue) + if (empty($value = $_COOKIE["markets_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['markets']['filters'][$name] ?? 0)) continue; + + // Генерация AQL-выражения для инъекции в строку запроса + if ($value === '1') $statuses_and .= " && market.$name == true"; + } + + // Очистка от бинарных операторов сравнения с только одним операндом (крайние) + $statuses_and = trim(trim(trim($statuses_and), '&&')); + + // Инициализация буфера AQL-выражения для инъекции фильтра по статусам (ИЛИ) + $statuses_or = ''; + + foreach ($statuses as $name) { + // Перебор фильтров статусов (ИЛИ) + + // Инициализация значения (приоритет у cookie) (отсутствие значения или значение 0 вызывают continue) + if (empty($value = $_COOKIE["markets_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['markets']['filters'][$name] ?? 0)) continue; + + // Генерация AQL-выражения для инъекции в строку запроса + if ($value === '2') $statuses_or .= " || market.$name == true"; + } + + // Очистка от бинарных операторов сравнения с только одним операндом (крайние) + $statuses_or = trim(trim(trim($statuses_or), '||')); + + // Инициализация буфера с объёдинёнными буферами c AQL-выражениям "И" и "ИЛИ" + $statuses_merged = (empty($statuses_and) ? '' : "($statuses_and)") . (empty($statuses_or) ? '' : (empty($statuses_and) ? '' : ' || ') . "($statuses_or)"); + + // Инициализация общего буфера с AQL-выражениями + $filters = ''; + + // Объединение фильров в единую строку с AQL-выражениями для инъекции + if (!empty($statuses_merged)) $filters .= empty($filters) ? $statuses_merged : " && ($statuses_merged)"; + + // Инициализация данных для генерации HTML-документа с таблицей + $this->view->rows = model::list(before: empty($filters) ? null : "FILTER ($filters)", page: (int) $this->session->buffer[$_SERVER['INTERFACE']]['markets']['page']); + + // Запись в cookie (только таким методом можно записать "hostonly: true") + setcookie( + 'markets_page', + (string) $this->session->buffer[$_SERVER['INTERFACE']]['markets']['page'], + [ + 'expires' => strtotime('+1 hour'), + 'path' => '/', + 'secure' => true, + 'httponly' => false, + 'samesite' => 'strict' + ] + ); + + // Запись в глобальную переменную шаблонизатора обрабатываемой страницы + $this->view->page = $parameters['page']; + + // Инициализация блока + return $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'markets.html'); + } + + // Возврат (провал) + return null; + } + + /** + * Прочитать данные магазинов для + * + * @param array $parameters Параметры запроса + */ + public function datalist(array $parameters = []): ?string + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) { + // Авторизован аккаунт оператора или магазина + + // Инициализация данных магазинов + $this->view->markets = model::read(filter: 'd.status == "active"', amount: 10000, return: '{ id: d.id, director: d.director }'); + + // Универсализация + if ($this->view->markets instanceof _document) $this->view->markets = [$this->view->markets]; + + // Возврат (успех) + return $this->view->render(DIRECTORY_SEPARATOR . 'lists' . DIRECTORY_SEPARATOR . 'markets.html'); + } + + // Возврат (провал) + return null; + } +} diff --git a/mirzaev/ebala/system/controllers/session.php b/mirzaev/ebala/system/controllers/session.php new file mode 100755 index 0000000..2805bf4 --- /dev/null +++ b/mirzaev/ebala/system/controllers/session.php @@ -0,0 +1,629 @@ + + */ +final class session extends core +{ + use errors; + + /** + * Записать номер сотрудника во все буферы сессии + * + * Проверяет существование аккаунта сотрудника с этим номером + * и запоминает для использования в процессе аутентификации + * + * @param array $parameters Параметры запроса + * + * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами + */ + public function worker(array $parameters = []): void + { + // Инициализация буфера ответа + $buffer = []; + + // Инициализация реестра возвращаемых параметров + $return = explode(',', $parameters['return'], 50); + + try { + // Проверка наличия обязательных параметров + if (empty($parameters['worker'])) throw new exception('Необходимо передать номер'); + + // Очистка всего кроме цифр, а потом поиск 10 первых чисел (без восьмёрки) + preg_match('/^\d(\d{10})/', preg_replace("/[^\d]/", "", $parameters['worker']), $matches); + + // Инициализация номера + $parameters['worker'] = isset($matches[1]) ? 7 . $matches[1] : $parameters['worker']; + + // Вычисление длины + $length = strlen($parameters['worker']); + + // Проверка параметров на соответствование требованиям + if ($length === 0) throw new exception('Номер не может быть пустым'); + if ($length != 11) throw new exception('Номер должен иметь 11 цифр'); + if (preg_match_all('/[^\d\(\)\-\s\r\n\t\0]+/u', $parameters['worker'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches)); + + if ($remember = isset($parameters['remember']) && $parameters['remember'] === '1') { + // Запрошено запоминание + + // Запись в cookie + setcookie('entry_number', $parameters['worker'], [ + 'expires' => strtotime('+1 day'), + 'path' => '/', + 'secure' => true, + 'httponly' => true, + 'samesite' => 'strict' + ]); + } + + // Поиск аккаунта + $account = account::read('d.number == "' . $parameters['worker'] . '"', amount: 1); + + // Генерация ответа по запрашиваемым параметрам + foreach ($return as $parameter) match ($parameter) { + 'exist' => $buffer['exist'] = isset($account), + 'account' => (function () use ($parameters, $remember, &$buffer) { + // Запись в буфер сессии + if ($remember) $this->session->write(['entry' => ['number' => $parameters['worker']]], $this->errors); + + // Поиск аккаунта и запись в буфер вывода + $buffer['account'] = (new account($this->session, 'worker', $this->errors))?->instance() instanceof _document; + })(), + 'verify' => $buffer['verify'] = true, + 'errors' => null, + default => throw new exception("Параметр не найден: $parameter") + }; + } catch (exception $e) { + // Запись в реестр ошибок + $this->errors['session'][] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Запись реестра ошибок в буфер ответа + if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors); + + // Запись заголовков ответа + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Инициализация буфера вывода + ob_start(); + + // Генерация ответа + echo json_encode($buffer); + + // Запись заголовков ответа + header('Content-Length: ' . ob_get_length()); + + // Отправка и деинициализация буфера вывода + ob_end_flush(); + flush(); + + // Запись в буфер сессии + if (!in_array('account', $return, true) && ($remember ?? false)) + $this->session->write(['entry' => ['number' => $parameters['worker']]]); + } + + /** + * Записать идентификатор администратора во все буферы сессии + * + * Проверяет существование аккаунта администратора с этим идентификатором + * и запоминает для использования в процессе аутентификации + * + * @param array $parameters Параметры запроса + * + * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами + */ + public function administrator(array $parameters = []): void + { + // Инициализация буфера ответа + $buffer = []; + + // Инициализация реестра возвращаемых параметров + $return = explode(',', $parameters['return'], 50); + + try { + // Проверка наличия обязательных параметров + if (empty($parameters['administrator'])) throw new exception('Необходимо передать идентификатор'); + + // Очистка всего кроме цифр, а потом поиск 10 первых чисел (без восьмёрки) + preg_match('/^\d{3,12}/', preg_replace("/[^\d]/", "", $parameters['administrator']), $matches); + + // Инициализация номера + $parameters['administrator'] = $matches[0]; + + // Вычисление длины + $length = strlen($parameters['administrator']); + + // Проверка параметров на соответствование требованиям + if ($length === 0) throw new exception('Идентификатор не может быть пустым'); + if ($length > 12) throw new exception('Идентификатор должен иметь не более 12 цифр'); + if (preg_match_all('/[^\d\(\)\-\s\r\n\t\0]+/u', $parameters['administrator'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches)); + + if ($remember = isset($parameters['remember']) && $parameters['remember'] === '1') { + // Запрошено запоминание + + // Запись в cookie + setcookie( + 'entry__key', + $parameters['administrator'], + [ + 'expires' => strtotime('+1 day'), + 'path' => '/', + 'secure' => true, + 'httponly' => true, + 'samesite' => 'strict' + ] + ); + } + + // Поиск аккаунта + $account = account::read('d._key == "' . $parameters['administrator'] . '"', amount: 1); + + // Генерация ответа по запрашиваемым параметрам + foreach ($return as $parameter) match ($parameter) { + 'exist' => $buffer['exist'] = isset($account), + 'account' => (function () use ($parameters, $remember, &$buffer) { + // Запись в буфер сессии + if ($remember) $this->session->write(['entry' => ['_key' => $parameters['administrator']]], $this->errors); + + // Поиск аккаунта и запись в буфер вывода + $buffer['account'] = (new account($this->session, 'administrator', $this->errors))?->instance() instanceof _document; + })(), + 'verify' => $buffer['verify'] = true, + 'errors' => null, + default => throw new exception("Параметр не найден: $parameter") + }; + } catch (exception $e) { + // Запись в реестр ошибок + $this->errors['session'][] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Запись реестра ошибок в буфер ответа + if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors); + + // Запись заголовков ответа + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Инициализация буфера вывода + ob_start(); + + // Генерация ответа + echo json_encode($buffer); + + // Запись заголовков ответа + header('Content-Length: ' . ob_get_length()); + + // Отправка и деинициализация буфера вывода + ob_end_flush(); + flush(); + + // Запись в буфер сессии + if (!in_array('account', $return, true) && ($remember ?? false)) + $this->session->write(['entry' => ['_key' => $parameters['administrator']]]); + } + + /** + * Записать идентификатор оператора во все буферы сессии + * + * Проверяет существование аккаунта оператора с этим идентификатором + * и запоминает для использования в процессе аутентификации + * + * @param array $parameters Параметры запроса + * + * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами + */ + public function operator(array $parameters = []): void + { + // Инициализация буфера ответа + $buffer = []; + + // Инициализация реестра возвращаемых параметров + $return = explode(',', $parameters['return'], 50); + + try { + // Проверка наличия обязательных параметров + if (empty($parameters['operator'])) throw new exception('Необходимо передать идентификатор'); + + // Очистка всего кроме цифр, а потом поиск 10 первых чисел (без восьмёрки) + preg_match('/^\d{3,12}/', preg_replace("/[^\d]/", "", $parameters['operator']), $matches); + + // Инициализация номера + $parameters['operator'] = $matches[0]; + + // Вычисление длины + $length = strlen($parameters['operator']); + + // Проверка параметров на соответствование требованиям + if ($length === 0) throw new exception('Идентификатор не может быть пустым'); + if ($length > 12) throw new exception('Идентификатор должен иметь не более 12 цифр'); + if (preg_match_all('/[^\d\(\)\-\s\r\n\t\0]+/u', $parameters['operator'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches)); + + if ($remember = isset($parameters['remember']) && $parameters['remember'] === '1') { + // Запрошено запоминание + + // Запись в cookie + setcookie('entry__key', $parameters['operator'], [ + 'expires' => strtotime('+1 day'), + 'path' => '/', + 'secure' => true, + 'httponly' => true, + 'samesite' => 'strict' + ]); + } + + // Поиск аккаунта + $account = account::read('d._key == "' . $parameters['operator'] . '"', amount: 1); + + // Генерация ответа по запрашиваемым параметрам + foreach ($return as $parameter) match ($parameter) { + 'exist' => $buffer['exist'] = isset($account), + 'account' => (function () use ($parameters, $remember, &$buffer) { + // Запись в буфер сессии + if ($remember) $this->session->write(['entry' => ['_key' => $parameters['operator']]], $this->errors); + + // Поиск аккаунта и запись в буфер вывода + $buffer['account'] = (new account($this->session, 'operator', $this->errors))?->instance() instanceof _document; + })(), + 'verify' => $buffer['verify'] = true, + 'errors' => null, + default => throw new exception("Параметр не найден: $parameter") + }; + } catch (exception $e) { + // Запись в реестр ошибок + $this->errors['session'][] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Запись реестра ошибок в буфер ответа + if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors); + + // Запись заголовков ответа + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Инициализация буфера вывода + ob_start(); + + // Генерация ответа + echo json_encode($buffer); + + // Запись заголовков ответа + header('Content-Length: ' . ob_get_length()); + + // Отправка и деинициализация буфера вывода + ob_end_flush(); + flush(); + + // Запись в буфер сессии + if (!in_array('account', $return, true) && ($remember ?? false)) + $this->session->write(['entry' => ['_key' => $parameters['operator']]]); + } + + /** + * Записать идентификатор магазина во все буферы сессии + * + * Проверяет существование аккаунта магазина с этим идентификатором + * и запоминает для использования в процессе аутентификации + * + * @param array $parameters Параметры запроса + * + * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами + */ + public function market(array $parameters = []): void + { + // Инициализация буфера ответа + $buffer = []; + + // Инициализация реестра возвращаемых параметров + $return = explode(',', $parameters['return'], 50); + + try { + // Проверка наличия обязательных параметров + if (empty($parameters['market'])) throw new exception('Необходимо передать идентификатор'); + + // Вычисление длины + $length = strlen($parameters['market']); + + // Проверка параметров на соответствование требованиям + if ($parameters['market'][0] !== 'K') throw new exception('Идентификатор должен начинаться с английской буквы "K"'); + if ($length <= 1) throw new exception('Идентификатор не может быть пустым'); + if ($length > 40) throw new exception('Идентификатор должен иметь не более 40 символов'); + if (preg_match_all('/[^\dK]+/u', $parameters['market'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches)); + + if ($remember = isset($parameters['remember']) && $parameters['remember'] === '1') { + // Запрошено запоминание + + // Запись в cookie + setcookie('entry_id', $parameters['market'], [ + 'expires' => strtotime('+1 day'), + 'path' => '/', + 'secure' => true, + 'httponly' => true, + 'samesite' => 'strict' + ]); + } + + // Поиск магазина + $market = market::read('d.id == "' . $parameters['market'] . '"', amount: 1); + + // Поиск аккаунта + $account = market::account($market->getId()); + + // Генерация ответа по запрашиваемым параметрам + foreach ($return as $parameter) match ($parameter) { + 'exist' => $buffer['exist'] = isset($account), + 'account' => (function () use ($parameters, $remember, &$buffer) { + // Запись в буфер сессии + if ($remember) $this->session->write(['entry' => ['id' => $parameters['market']]], $this->errors); + + // Поиск аккаунта и запись в буфер вывода + $buffer['account'] = (new account($this->session, 'market', $this->errors))?->instance() instanceof _document; + })(), + 'verify' => $buffer['verify'] = true, + 'errors' => null, + default => throw new exception("Параметр не найден: $parameter") + }; + } catch (exception $e) { + // Запись в реестр ошибок + $this->errors['session'][] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Запись реестра ошибок в буфер ответа + if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors); + + // Запись заголовков ответа + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Инициализация буфера вывода + ob_start(); + + // Генерация ответа + echo json_encode($buffer); + + // Запись заголовков ответа + header('Content-Length: ' . ob_get_length()); + + // Отправка и деинициализация буфера вывода + ob_end_flush(); + flush(); + + // Запись в буфер сессии + if (!in_array('account', $return, true) && ($remember ?? false)) + $this->session->write(['entry' => ['id' => $parameters['market']]]); + } + + /** + * Записать пароль любого типа аккаунта во все буферы сессии + * + * Проверяет на соответствие требованиям + * и запоминает для использования в процессе аутентификации + * + * @param array $parameters Параметры запроса + * + * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами + */ + public function password(array $parameters = []): void + { + // Инициализация буфера ответа + $buffer = []; + + // Инициализация реестра возвращаемых параметров + $return = explode(',', $parameters['return'], 50); + + try { + // Вычисление длины + $length = strlen($parameters['password']); + + // Проверка параметров на соответствование требованиям + if ($length > 300) throw new exception('Пароль не может быть длиннее 300 символов'); + if (preg_match_all('/[^\w\s\r\n\t\0]+/u', $parameters['password'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches)); + + // Инициализация значения по умолчанию для типа аккаунта + $parameters['type'] ??= 'worker'; + + // Генерация ответа по запрашиваемым параметрам + foreach ($return as $parameter) match ($parameter) { + 'account' => (function () use ($parameters, &$buffer) { + // Запись в буфер сессии + if (isset($parameters['remember']) && $parameters['remember'] === '1') + $this->session->write(['entry' => ['password' => $parameters['password']]], $this->errors); + + // Поиск аккаунта и запись в буфер вывода + $buffer['account'] = (new account($this->session, $parameters['type'], $this->errors))?->instance() instanceof _document; + })(), + 'verify' => $buffer['verify'] = true, + 'errors' => null, + default => throw new exception("Параметр не найден: $parameter") + }; + } catch (exception $e) { + // Запись в реестр ошибок + $this->errors['session'][] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Запись реестра ошибок в буфер ответа + if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors); + + // Запись заголовков ответа + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Инициализация буфера вывода + ob_start(); + + // Генерация ответа + echo json_encode($buffer); + + // Запись заголовков ответа + header('Content-Length: ' . ob_get_length()); + + // Отправка и деинициализация буфера вывода + ob_end_flush(); + flush(); + + // Запись в буфер сессии + if (!in_array('account', $return, true) && isset($parameters['remember']) && $parameters['remember'] === '1') + $this->session->write(['entry' => ['password' => $parameters['password']]]); + } + + /** + * Записать в буфер сессии + * + * @param array $parameters Параметры запроса + * + * @return void + */ + public function write(array $parameters = []): void + { + try { + if ($this->account->status()) { + // Авторизован аккаунт + + // Инициализация директорий для генерации + $directories = explode('_', $parameters['name'], 100); + + // Инициализированы директории? + if (count($directories) === 0) return; + + // Конвертация: filter -> filters + if ($directories[1] === 'filter') $directories[1] .= 's'; + + // Инициализация буфера вывода + $response = []; + + // Инициализация буфера выполнения + $buffer = &$response; + + foreach ($directories as $directory) { + // Перебор директорий + + // Инициализация структуры + $buffer[$directory] = ($next = next($directories) === false) ? $parameters['value'] : []; + + // Реинициализация цели для инициализации структуры (углубление в массив) + if ($next) unset($buffer); + else $buffer = &$buffer[$directory]; + } + + // Запись в буфер сессии + $this->session->write($response); + } + } catch (exception $e) { + // Запись в реестр ошибок + $this->errors['session'][] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + } + + /** + * Прочитать из буфера сессии + * + * @param array $parameters Параметры запроса + * + * @return ?string Данные из буфера сессии, если найдены + */ + public function read(array $parameters = []): ?string + { + try { + if ($this->account->status()) { + // Авторизован аккаунт + + // Инициализация директорий для генерации + $directories = explode('_', $parameters['name'], 100); + + // Инициализированы директории? + if (count($directories) === 0) return null; + + // Конвертация: filter -> filters + if ($directories[1] === 'filter') $directories[1] .= 's'; + + // Инициализация буфера хранилища + $storage = $this->session->buffer[$_SERVER['INTERFACE']]; + + // Инициализация буфера выполнения + $buffer = &$storage[reset($directories)] ?? null; + + // Найдена первая директория в базе данных? + if (isset($buffer) === 0) return null; + + foreach ($directories as &$directory) { + // Перебор директорий + + // Инициализация новой целевой директории + if (isset($buffer[$directory])) $buffer = &$buffer[$directory]; + else continue; + + // Реинициализация цели для инициализации структуры (углубление в массив) + if (next($directories) === false) $buffer = $buffer[$directory]; + } + + // Возврат (успех) + return is_array($buffer) ? null : $buffer; + } + } catch (exception $e) { + // Запись в реестр ошибок + $this->errors['session'][] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Возврат (провал) + return null; + } +} diff --git a/mirzaev/ebala/system/controllers/task.php b/mirzaev/ebala/system/controllers/task.php new file mode 100755 index 0000000..a46ee5a --- /dev/null +++ b/mirzaev/ebala/system/controllers/task.php @@ -0,0 +1,1095 @@ + + */ +final class task extends core +{ + use errors; + + /** + * Создать + * + * @param array $parameters Параметры запроса + * + * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами + */ + public function create(array $parameters = []): void + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) { + // Авторизован аккаунт администратора, оператора или магазина + + // Инициализация буфера ошибок + $this->errors['tasks'] ??= []; + + // Создание строк + for ($i = 0, $parameters['cashiers'] = (int) $parameters['cashiers']; $i < $parameters['cashiers']; ++$i) model::create(work: 'Кассир', market: $this->account->type === 'market' ? $this->account->id : null, start: $parameters['start'], end: $parameters['end'], date: $parameters['date'], errors: $this->errors['tasks']); + for ($i = 0, $parameters['displayers'] = (int) $parameters['displayers']; $i < $parameters['displayers']; ++$i) model::create(work: 'Выкладчик', market: $this->account->type === 'market' ? $this->account->id : null, start: $parameters['start'], end: $parameters['end'], date: $parameters['date'], errors: $this->errors['tasks']); + for ($i = 0, $parameters['loaders'] = (int) $parameters['loaders']; $i < $parameters['loaders']; ++$i) model::create(work: 'Грузчик', market: $this->account->type === 'market' ? $this->account->id : null, start: $parameters['start'], end: $parameters['end'], date: $parameters['date'], errors: $this->errors['tasks']); + for ($i = 0, $parameters['gastronomes'] = (int) $parameters['gastronomes']; $i < $parameters['gastronomes']; ++$i) model::create(work: 'Гастроном', market: $this->account->type === 'market' ? $this->account->id : null, start: $parameters['start'], end: $parameters['end'], date: $parameters['date'], errors: $this->errors['tasks']); + + // Запись заголовков ответа + 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(); + } + } + + /** + * Прочитать + * + * @param array $parameters Параметры запроса + */ + public function read(array $parameters = []): ?string + { + if ($this->account->status()) { + // Авторизован аккаунт + + // Реинициализация актуальной страницы + if (isset($parameters['page'])) $this->session->write(['tasks' => ['page' => $parameters['page']]]); + else if (empty($this->session->buffer[$_SERVER['INTERFACE']]['tasks']['page'])) $this->session->write(['tasks' => ['page' => 1]]); + + // Инициализация буфера AQL-выражения для инъекции фильтра по интервалу + $interval = ''; + + foreach (['from', 'to'] as $name) { + // Перебор фильтров временного промежутка (И) + + // Инициализация значения (приоритет у cookie) + if (empty($value = (int) ($_COOKIE["tasks_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters'][$name] ?? (($name === 'from') ? time() : strtotime('+1 month'))))) continue; + + // Генерация AQL-выражения для инъекции в строку запроса + if ($name === 'from') $interval .= " && task.date >= $value"; + else if ($name === 'to') $interval .= " && task.date <= $value"; + } + + // Очистка от бинарных операторов сравнения с только одним операндом (крайние) + $interval = trim(trim(trim($interval), '&&')); + + // Инициализация буфера AQL-выражения для инъекции фильтра по интервалу + $polysemantic = ''; + + foreach (['rating'] as $name) { + // Перебор фильтров с произвольными значениями (И) + + // Инициализация значения (приоритет у cookie) + $value = $_COOKIE["tasks_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters'][$name] ?? null; + + // Найдено значение? + if ($value === null) continue; + + // Генерация AQL-выражения для инъекции в строку запроса + if ($name === 'rating' && $value > 0) $polysemantic .= " && task.rating >= $value"; + } + + // Очистка от бинарных операторов сравнения с только одним операндом (крайние) + $polysemantic = trim(trim(trim($polysemantic), '&&')); + + // Инициализация допустимых статусов + // @TODO Добавить инвертирование для waiting и unpublished + $statuses = ['confirmed', 'waiting', 'published', 'unpublished', 'problematic', 'hided', 'completed']; + + // Инициализация буфера AQL-выражения для инъекции фильтра по статусам (И) + $statuses_and = ''; + + foreach ($statuses as $name) { + // Перебор фильтров статусов (И) + + // Инициализация значения (приоритет у cookie) (отсутствие значения или значение 0 вызывают continue) + if (empty($value = $_COOKIE["tasks_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters'][$name] ?? 0)) continue; + + // Генерация AQL-выражения для инъекции в строку запроса + if ($value === '1') $statuses_and .= " && task.$name == true"; + } + + // Очистка от бинарных операторов сравнения с только одним операндом (крайние) + $statuses_and = trim(trim(trim($statuses_and), '&&')); + + // Инициализация буфера AQL-выражения для инъекции фильтра по статусам (ИЛИ) + $statuses_or = ''; + + foreach ($statuses as $name) { + // Перебор фильтров статусов (ИЛИ) + + // Инициализация значения (приоритет у cookie) (отсутствие значения или значение 0 вызывают continue) + if (empty($value = $_COOKIE["tasks_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters'][$name] ?? 0)) continue; + + // Генерация AQL-выражения для инъекции в строку запроса + if ($value === '2') $statuses_or .= " || task.$name == true"; + } + + // Очистка от бинарных операторов сравнения с только одним операндом (крайние) + $statuses_or = trim(trim(trim($statuses_or), '||')); + + // Инициализация буфера с объёдинёнными буферами c AQL-выражениям "И" и "ИЛИ" + $statuses_merged = (empty($statuses_and) ? '' : "($statuses_and)") . (empty($statuses_or) ? '' : (empty($statuses_and) ? '' : ' || ') . "($statuses_or)"); + + // Инициализация общего буфера с AQL-выражениями + $filters = ''; + + // Объединение фильров в единую строку с AQL-выражениями для инъекции + if (!empty($interval)) $filters .= $interval; + if (!empty($statuses_merged)) $filters .= empty($filters) ? $statuses_merged : " && ($statuses_merged)"; + if (!empty($polysemantic)) $filters .= empty($filters) ? $polysemantic : " && $polysemantic"; + + // Инициализация данных для генерации HTML-документа с таблицей + if ($_SERVER['INTERFACE'] === 'worker') + $this->view->rows = model::list(before: 'FILTER task.worker == "' . account::worker($this->account->number)?->id . '"' . empty($filters) ? null : " && ($filters)", page: (int) $this->session->buffer['worker']['tasks']['page']); + else if ($_SERVER['INTERFACE'] === 'operator') + $this->view->rows = model::list(before: empty($filters) ? null : "FILTER ($filters)", page: (int) $this->session->buffer['operator']['tasks']['page']); + else if ($_SERVER['INTERFACE'] === 'market') + $this->view->rows = model::list(before: 'FILTER task.market == "' . $this->market->id . '"' . empty($filters) ? null : " && ($filters)", page: (int) $this->session->buffer['market']['tasks']['page']); + else if ($_SERVER['INTERFACE'] === 'administrator') + $this->view->rows = model::list(before: empty($filters) ? null : "FILTER ($filters)", page: (int) $this->session->buffer['administrator']['tasks']['page']); + else $this->view->rows = []; + + // Запись в cookie (только таким методом можно записать "hostonly: true") + setcookie( + 'tasks_page', + (string) $this->session->buffer[$_SERVER['INTERFACE']]['tasks']['page'], + [ + 'expires' => strtotime('+1 hour'), + 'path' => '/', + 'secure' => true, + 'httponly' => false, + 'samesite' => 'strict' + ] + ); + + // Предобработка строк перед генерацией документа + $this->view->rows = static::preprocessing($this->view->rows); + + // Запись в глобальную переменную шаблонизатора обрабатываемой страницы + $this->view->page = $parameters['page']; + + // Инициализация блока + return $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'); + } + + // Возврат (провал) + return null; + } + + /** + * Предобработка строк перед генерацией документа + * + * @param array $rows Строки + * + * @return array Обработанные строки + */ + public static function preprocessing(array $rows): array + { + // Инициализация буфера прочитанных из базы данных строк + $buffer = $rows; + + foreach ($rows as $number => $row) { + // Перебор прочитанных из базы данных строк + + // Инициализация ярлыка + $link = &$buffer[$number]; + + // Конвертация времён + if (!empty($row->task['start'])) $link->task = ['start' => datetime::createFromFormat('H:i', (string) $row->task['start'])] + $link->task; + if (!empty($row->task['end'])) $link->task = ['end' => datetime::createFromFormat('H:i', (string) $row->task['end'])] + $link->task; + + // Инициализация буфера сгенерированных данных работы для шаблонизатора + $generated = []; + + // Генерация данных работы для шаблонизатора + if ( + $link->task['start'] instanceof datetime + && $link->task['end'] instanceof datetime + ) { + $generated['hours'] = (float) $link->task['end']->diff($link->task['start'])->format('%H.%i'); + if ($generated['hours'] >= 6.5 && $generated['hours'] < 9) $generated['hours'] -= 0.5; + else if ($generated['hours'] >= 9 && $generated['hours'] < 12.5) $generated['hours'] -= 1; + else if ($generated['hours'] >= 12.5) $generated['hours'] -= 1.5; + } + if ($link->task['start'] instanceof datetime) $generated['start'] = $link->task['start']->format('H:i'); + if ($link->task['end'] instanceof datetime) $generated['end'] = $link->task['end']->format('H:i'); + + // Запись из буфера сгенерированных данных работы для шаблонизатора в буфер переменных окружения шаблонизатора + $link->task = ['generated' => $generated] + $link->task; + } + + // Трансфер строк из модифицированного буфера процесса в буфер вывода + return $buffer; + } + + /** + * Заменить сотрудника или магазин + * + * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами + */ + public function update(array $parameters = []): void + { + try { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) { + // Авторизован аккаунт администратора, оператора или магазина + + if (($task = model::read('d._key == "' . $parameters['task'] . '"', amount: 1)) instanceof _document) { + if (!empty($parameters['worker'])) { + // Передан сотрудник + if ($parameters['worker'] === 'delete') { + // Удалить сотрудника + + // Удаление сотрудника + $task->worker = null; + + if (_core::update($task)) { + // Записано изменение в базу данных + + // Инициализация строки в глобальную переменную шаблонизатора + $this->view->rows = static::preprocessing(model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1)); + + // Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение) + $this->view->page = null; + + // Запись заголовков ответа + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Инициализация буфера вывода + ob_start(); + + // Генерация ответа + echo json_encode( + [ + 'updated' => true, + 'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'), + 'errors' => self::parse_only_text($this->errors) + ] + ); + + // Запись заголовков ответа + header('Content-Length: ' . ob_get_length()); + + // Отправка и деинициализация буфера вывода + ob_end_flush(); + flush(); + } else throw new exception('Не удалось записать изменения в базу данных'); + } else { + // Записать нового сотрудника + + if (($worker = worker::read('d.id == "' . $parameters['worker'] . '" && d.status == "active"', amount: 1)) instanceof _document) { + // Найден сотрудник (запрашиваемый для записи сотрудник существует в базе данных) + + if ($task->worker !== $parameters['worker']) { + // Идентификатор запрашиваемого сотрудника не равен актуальному + + // Запись сотрудника + $task->worker = $worker->id; + + if (_core::update($task)) { + // Записано изменение в базу данных + + // Инициализация строки в глобальную переменную шаблонизатора + $this->view->rows = static::preprocessing(model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1)); + + // Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение) + $this->view->page = null; + + // Запись заголовков ответа + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Инициализация буфера вывода + ob_start(); + + // Генерация ответа + echo json_encode( + [ + 'updated' => true, + 'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'), + 'errors' => self::parse_only_text($this->errors) + ] + ); + + // Запись заголовков ответа + header('Content-Length: ' . ob_get_length()); + + // Отправка и деинициализация буфера вывода + ob_end_flush(); + flush(); + } else throw new exception('Не удалось записать изменения в базу данных'); + } else throw new exception('Сотрудник уже назначен'); + } else throw new exception('Не найден сотрудник'); + } + } else if (!empty($parameters['market'])) { + // Передан магазин + + if ($parameters['market'] === 'delete') { + // Удалить магазин + + // Удаление магазина + $task->market = null; + + if (_core::update($task)) { + // Записано изменение в базу данных + + // Инициализация строки в глобальную переменную шаблонизатора + $this->view->rows = static::preprocessing(model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1)); + + // Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение) + $this->view->page = null; + + // Запись заголовков ответа + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Инициализация буфера вывода + ob_start(); + + // Генерация ответа + echo json_encode( + [ + 'updated' => true, + 'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'), + 'errors' => self::parse_only_text($this->errors) + ] + ); + + // Запись заголовков ответа + header('Content-Length: ' . ob_get_length()); + + // Отправка и деинициализация буфера вывода + ob_end_flush(); + flush(); + } else throw new exception('Не удалось записать изменения в базу данных'); + } else { + // Записать новый магазин + + if (($market = market::read('d.id == "' . $parameters['market'] . '" && d.status == "active"', amount: 1)) instanceof _document) { + // Найден магазин (запрашиваемый для записи магазин существует в базе данных) + + if ($task->market !== $parameters['market']) { + // Идентификатор запрашиваемого сотрудника не равен актуальному + + // Запись магазина + $task->market = $market->id; + + if (_core::update($task)) { + // Записано изменение в базу данных + + // Инициализация строки в глобальную переменную шаблонизатора + $this->view->rows = static::preprocessing(model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1)); + + // Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение) + $this->view->page = null; + + // Запись заголовков ответа + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Инициализация буфера вывода + ob_start(); + + // Генерация ответа + echo json_encode( + [ + 'updated' => true, + 'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'), + 'errors' => self::parse_only_text($this->errors) + ] + ); + + // Запись заголовков ответа + header('Content-Length: ' . ob_get_length()); + + // Отправка и деинициализация буфера вывода + ob_end_flush(); + flush(); + } else throw new exception('Не удалось записать изменения в базу данных'); + } else throw new exception('Магазин уже назначен'); + } else throw new exception('Не найден магазин'); + } + } else throw new exception('Не получены данные для записи'); + } else throw new exception('Не найдено задание'); + } else throw new exception('Вы не авторизованы'); + } 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( + [ + 'updated' => false, + 'errors' => self::parse_only_text($this->errors) + ] + ); + + // Запись заголовков ответа + header('Content-Length: ' . ob_get_length()); + + // Отправка и деинициализация буфера вывода + ob_end_flush(); + flush(); + } + } + + + /** + * Прочитать данные задачи + * + * @param array $parameters Параметры запроса + */ + public function task(array $parameters = []): ?string + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) { + // Авторизован аккаунт оператора + + // Инициализация данных сотрудника + $this->view->task = model::read('d._key == "' . $parameters['task'] . '"', return: '{_key: d._key, created: d.created, updated: d.updated, confirmed: d.confirmed, hided: d.hided }')->getAll(); + + if (!empty($this->view->task)) { + // Найдены данные сотрудника + + // Инициализация буфера данных сотрудника + $buffer = []; + + // Перевод ключей на русский язык + foreach ($this->view->task as $key => $value) + if (match ($key) { + 'created', 'updated', 'confirmed', 'hided', '_key' => true, + default => false + }) $buffer[$key] = [ + 'label' => match ($key) { + 'created' => 'Создано', + 'updated' => 'Обновлено', + 'confirmed' => 'Подтверждено', + 'hided' => 'Скрыто', + default => $key + }, + 'value' => $value + ]; + + // Запись из буфера данных сотрудника + $this->view->task = $buffer; + + // Возврат (успех) + return $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'lists' . DIRECTORY_SEPARATOR . 'task.html'); + } + } + + // Возврат (провал) + return null; + } + + + /** + * Прочитать данные сотрудника + * + * @param array $parameters Параметры запроса + */ + public function worker(array $parameters = []): ?string + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) { + // Авторизован аккаунт оператора + + // Инициализация данных сотрудника + $this->view->worker = worker::read('d.id == "' . $parameters['worker'] . '"')->getAll(); + + if (!empty($this->view->worker)) { + // Найдены данные сотрудника + + // Инициализация буфера данных сотрудника + $buffer = []; + + // Перевод ключей на русский язык + foreach ($this->view->worker as $key => $value) $buffer[$key] = [ + 'label' => match ($key) { + 'created' => 'Создано', + 'updated' => 'Обновлено', + 'id' => 'Идентификатор', + 'phone', 'number' => 'Номер', + 'birth' => 'Дата рождения', + 'address' => 'Адрес', + 'activity' => 'Деятельность', + 'passport' => 'Пасорт', + 'issued' => 'Место выдачи', + 'hiring' => 'Дата найма', + 'district' => 'Адрес регистрации', + 'department' => 'Отделение', + 'requisites' => 'Реквизиты', + 'fired' => 'Уволен', + 'tax' => 'ИНН', + 'city' => 'Город', + 'commentary' => 'Комментарий', + 'payment' => 'Получатель', + default => $key + }, + 'value' => $value + ]; + + // Запись из буфера данных + $this->view->worker = $buffer; + + // Возврат (успех) + return $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'lists' . DIRECTORY_SEPARATOR . 'worker.html'); + } + } + + // Возврат (провал) + return null; + } + + /** + * Прочитать данные магазина + * + * @param array $parameters Параметры запроса + */ + public function market(array $parameters = []): ?string + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) { + // Авторизован аккаунт оператора + + // Инициализация данных + $this->view->market = market::read('CONCAT("K", SUBSTRING(d.id, 1)) == "' . $parameters['market'] . '"')?->getAll(); + + if (!empty($this->view->market)) { + // Найдены данные магазина + + // Инициализация буфера данных + $buffer = []; + + // Перевод ключей на русский язык + foreach ($this->view->market as $key => $value) $buffer[$key] = [ + 'label' => match ($key) { + 'created' => 'Создано', + 'updated' => 'Обновлено', + 'id' => 'Идентификатор', + 'type' => 'Тип', + 'director' => 'Директор', + 'address' => 'Адрес', + 'city' => 'Город', + default => $key + }, + 'value' => $value + ]; + + // Запись из буфера данных + $this->view->market = $buffer; + + // Возврат (успех) + return $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'lists' . DIRECTORY_SEPARATOR . 'market.html'); + } + } + + // Возврат (провал) + return null; + } + + /** + * Подтвердить (отклонить) + * + * @param array $parameters Параметры запроса + * + * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами + */ + public function confirm(array $parameters = []): void + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) { + // Авторизован аккаунт оператора + + // Инициализация данных + $task = model::read('d._key == "' . $parameters['task'] . '"'); + + if ($task instanceof _document) { + // Найдена заявка + + // Изменение статуса подтверждения + $task->confirmed = !$task->confirmed; + + if (_core::update($task)) { + // Записано изменение в базу данных + + // Инициализация строки в глобальную переменную шаблонизатора + $this->view->rows = static::preprocessing(model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1)); + + // Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение) + $this->view->page = null; + + // Запись заголовков ответа + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Инициализация буфера вывода + ob_start(); + + // Генерация ответа + echo json_encode( + [ + 'confirmed' => $this->view->rows[0]->task['confirmed'], + '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(); + } + } + } + } + + /** + * Скрыть (показать) + * + * @param array $parameters Параметры запроса + * + * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами + */ + public function hide(array $parameters = []): void + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) { + // Авторизован аккаунт оператора + + // Инициализация данных + $task = model::read('d._key == "' . $parameters['task'] . '"'); + + if ($task instanceof _document) { + // Найдена заявка + + // Изменение статуса скрытия + $task->hided = !$task->hided; + + if (_core::update($task)) { + // Записано изменение в базу данных + + // Инициализация строки в глобальную переменную шаблонизатора + $this->view->rows = static::preprocessing(model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1)); + + // Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение) + $this->view->page = null; + + // Запись заголовков ответа + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Инициализация буфера вывода + ob_start(); + + // Генерация ответа + echo json_encode( + [ + 'hided' => $this->view->rows[0]->task['hided'], + '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(); + } + } + } + } + + /** + * Удалить + * + * @param array $parameters Параметры запроса + * + * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами + */ + public function remove(array $parameters = []): void + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) { + // Авторизован аккаунт оператора + + // Инициализация данных + $task = model::read('d._key == "' . $parameters['task'] . '"'); + + if ($task instanceof _document) { + // Найдена заявка + + // Изменение статуса + $task->status = 'deleted'; + + if (_core::update($task)) { + // Помечено как удалённое + + // Запись заголовков ответа + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Инициализация буфера вывода + ob_start(); + + // Генерация ответа + echo json_encode( + [ + 'deleted' => true, + 'errors' => self::parse_only_text($this->errors) + ] + ); + + // Запись заголовков ответа + header('Content-Length: ' . ob_get_length()); + + // Отправка и деинициализация буфера вывода + ob_end_flush(); + flush(); + } + } + } + } + + /** + * Прочитать значение по названию + * + * @param array $parameters Параметры запроса + * + * @return mixed Значение из инстанции задачи в базе данных по имени + */ + public function value(array $parameters = []): mixed + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) { + // Авторизован аккаунт оператора или магазина + + // Инициализация данных + $task = model::read('d._key == "' . $parameters['task'] . '"'); + + if ($task instanceof _document) { + // Найдена заявка + + // Возврат (успех) + return $task->{$parameters['name']} ?? null; + } + } + + // Возврат (провал) + return null; + } + + + /** + * Прочитать данные работ для + * + * @param array $parameters Параметры запроса + */ + public function works(array $parameters = []): ?string + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) { + // Авторизован аккаунт оператора или магазина + + // Инициализация данных + $this->view->task = model::read('d._key == "' . $parameters['task'] . '"'); + + if ($this->view->task instanceof _document) { + // Найдена заявка + + // Инициализация списка работ + $this->view->works = ['Кассир', 'Выкладчик', 'Грузчик', 'Гастроном']; + + // Проверка на существование записанной в задаче работы в списке существующих работ и запись об этом в глобальную переменную шаблонизатора + foreach ($this->view->works as $work) if ($this->view->task->work === $work) $this->view->exist = true; + + // Возврат (успех) + return $this->view->render(DIRECTORY_SEPARATOR . 'lists' . DIRECTORY_SEPARATOR . 'works.html'); + } + } + + // Возврат (провал) + return null; + } + + /** + * Записать тип работы + * + * @param array $parameters Параметры запроса + * + * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами + */ + public function work(array $parameters = []): void + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) { + // Авторизован аккаунт оператора + + // Инициализация данных + $task = model::read('d._key == "' . $parameters['task'] . '"'); + + if ($task instanceof _document) { + // Найдена заявка + + // Изменение статуса + $task->work = match ($parameters['work']) { + 'Кассир', 'Выкладчик', 'Грузчик', 'Гастроном' => $parameters['work'], + default => 'Кассир' + }; + + if (_core::update($task)) { + // Записано в ArangoDB + + // Инициализация строки в глобальную переменную шаблонизатора + $this->view->rows = static::preprocessing(model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1)); + + // Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение) + $this->view->page = null; + + // Запись заголовков ответа + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Инициализация буфера вывода + ob_start(); + + // Генерация ответа + echo json_encode( + [ + 'writed' => true, + 'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'), + 'errors' => self::parse_only_text($this->errors) + ] + ); + + // Запись заголовков ответа + header('Content-Length: ' . ob_get_length()); + + // Отправка и деинициализация буфера вывода + ob_end_flush(); + flush(); + } + } + } + } + + /** + * Записать описание + * + * @param array $parameters Параметры запроса + * + * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами + */ + public function description(array $parameters = []): void + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) { + // Авторизован аккаунт оператора + + // Инициализация данных + $task = model::read('d._key == "' . $parameters['task'] . '"'); + + if ($task instanceof _document) { + // Найдена заявка + + // Изменение статуса + $task->description = $parameters['description']; + + if (_core::update($task)) { + // Записано в ArangoDB + + // Запись заголовков ответа + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Инициализация буфера вывода + ob_start(); + + // Генерация ответа + echo json_encode( + [ + 'writed' => true, + 'errors' => self::parse_only_text($this->errors) + ] + ); + + // Запись заголовков ответа + header('Content-Length: ' . ob_get_length()); + + // Отправка и деинициализация буфера вывода + ob_end_flush(); + flush(); + } + } + } + } + + /** + * Записать дату и время + * + * @param array $parameters Параметры запроса + * + * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами + */ + public function date(array $parameters = []): void + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) { + // Авторизован аккаунт оператора + + // Инициализация данных + $task = model::read('d._key == "' . $parameters['task'] . '"'); + + if ($task instanceof _document) { + // Найдена заявка + + // Запись даты и времени + if (!empty($parameters['date'])) $task->date = +$parameters['date']; + if (!empty($parameters['start'])) $task->start = $parameters['start']; + if (!empty($parameters['end'])) $task->end = $parameters['end']; + + if (_core::update($task)) { + // Записано в ArangoDB + + // Инициализация строки в глобальную переменную шаблонизатора + $this->view->rows = static::preprocessing(model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1)); + + // Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение) + $this->view->page = null; + + + // Запись заголовков ответа + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Инициализация буфера вывода + ob_start(); + + // Генерация ответа + echo json_encode( + [ + 'writed' => true, + 'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'), + 'errors' => self::parse_only_text($this->errors) + ] + ); + + // Запись заголовков ответа + header('Content-Length: ' . ob_get_length()); + + // Отправка и деинициализация буфера вывода + ob_end_flush(); + flush(); + } + } + } + } + + /** + * Записать комментарий + * + * @param array $parameters Параметры запроса + * + * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами + */ + public function commentary(array $parameters = []): void + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) { + // Авторизован аккаунт оператора + + // Инициализация данных + $task = model::read('d._key == "' . $parameters['task'] . '"'); + + if ($task instanceof _document) { + // Найдена заявка + + // Запись комментария + $task->commentary = $parameters['commentary']; + + if (_core::update($task)) { + // Записано в ArangoDB + + // Инициализация строки в глобальную переменную шаблонизатора + $this->view->rows = static::preprocessing(model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1)); + + // Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение) + $this->view->page = null; + + // Запись заголовков ответа + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Инициализация буфера вывода + ob_start(); + + // Генерация ответа + echo json_encode( + [ + 'writed' => true, + 'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'), + 'errors' => self::parse_only_text($this->errors) + ] + ); + + // Запись заголовков ответа + header('Content-Length: ' . ob_get_length()); + + // Отправка и деинициализация буфера вывода + ob_end_flush(); + flush(); + } + } + } + } +} diff --git a/mirzaev/ebala/system/controllers/traits/errors.php b/mirzaev/ebala/system/controllers/traits/errors.php new file mode 100755 index 0000000..bfb92e7 --- /dev/null +++ b/mirzaev/ebala/system/controllers/traits/errors.php @@ -0,0 +1,30 @@ + + */ +trait errors +{ + private static function parse_only_text(array $errors): array + { + // Инициализация буфера вывода + $buffer = []; + + foreach ($errors as $offset => $error) { + // Перебор ошибок + + // Проверка на вложенность и запись в буфер вывода (вход в рекурсию) + if (isset($error['text'])) $buffer[] = $error['text']; + else if (is_array($error) && count($error) > 0) $buffer[$offset] = static::parse_only_text($error); + } + + return $buffer; + } +} diff --git a/mirzaev/ebala/system/controllers/worker.php b/mirzaev/ebala/system/controllers/worker.php new file mode 100755 index 0000000..12ee91c --- /dev/null +++ b/mirzaev/ebala/system/controllers/worker.php @@ -0,0 +1,194 @@ + + */ +final class worker extends core +{ + use errors; + + /** + * Главная страница + * + * @param array $parameters Параметры запроса + */ + public function index(array $parameters = []): ?string + { + // Авторизация + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) { + // Авторизован аккаунт оператора или администратора + + foreach (['active', 'inactive', 'fined', 'decent', 'hided', 'fired'] as $name) { + // Перебор фильтров статусов + + // Инициализация значения (приоритет у cookie) + $value = $_COOKIE["workers_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['workers']['filters'][$name] ?? 0; + + // Инициализировано значение? + if ($value === null || $value === 0) continue; + + // Генерация класса для HTML-элемента по его состоянию (0 - ОТСУТСТВУЕТ, 1 - И, 2 - ИЛИ) + $this->view->{$name} = match ($value) { + '0', 0 => 'earth', + '1', 1 => 'sand', + '2', 2 => 'river', + default => 'earth' + }; + } + + // Генерация представлениямя + $main = $this->view->render(DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'workers.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; + } + + /** + * Прочитать + * + * @param array $parameters Параметры запроса + */ + public function read(array $parameters = []): ?string + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) { + // Авторизован аккаунт оператора или администратора + + // Реинициализация актуальной страницы + if (isset($parameters['page'])) $this->session->write(['workers' => ['page' => $parameters['page']]]); + else if (empty($this->session->buffer[$_SERVER['INTERFACE']]['workers']['page'])) $this->session->write(['workers' => ['page' => 1]]); + + // Инициализация буфера AQL-выражения для инъекции фильтра по интервалу + $polysemantic = ''; + + foreach (['rating'] as $name) { + // Перебор фильтров с произвольными значениями (И) + + // Инициализация значения (приоритет у cookie) + $value = $_COOKIE["workers_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['workers']['filters'][$name] ?? null; + + // Найдено значение? + if ($value === null) continue; + + // Генерация AQL-выражения для инъекции в строку запроса + if ($name === 'rating' && $value > 0) $polysemantic .= " && worker.rating >= $value"; + } + + // Очистка от бинарных операторов сравнения с только одним операндом (крайние) + $polysemantic = trim(trim(trim($polysemantic), '&&')); + + // Инициализация допустимых статусов + $statuses = ['active', 'inactive', 'fined', 'decent', 'hided', 'fired']; + + // Инициализация буфера AQL-выражения для инъекции фильтра по статусам (И) + $statuses_and = ''; + + foreach ($statuses as $name) { + // Перебор фильтров статусов (И) + + // Инициализация значения (приоритет у cookie) (отсутствие значения или значение 0 вызывают continue) + if (empty($value = $_COOKIE["workers_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['workers']['filters'][$name] ?? 0)) continue; + + // Генерация AQL-выражения для инъекции в строку запроса + if ($value === '1') $statuses_and .= " && worker.$name == true"; + } + + // Очистка от бинарных операторов сравнения с только одним операндом (крайние) + $statuses_and = trim(trim(trim($statuses_and), '&&')); + + // Инициализация буфера AQL-выражения для инъекции фильтра по статусам (ИЛИ) + $statuses_or = ''; + + foreach ($statuses as $name) { + // Перебор фильтров статусов (ИЛИ) + + // Инициализация значения (приоритет у cookie) (отсутствие значения или значение 0 вызывают continue) + if (empty($value = $_COOKIE["workers_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['workers']['filters'][$name] ?? 0)) continue; + + // Генерация AQL-выражения для инъекции в строку запроса + if ($value === '2') $statuses_or .= " || worker.$name == true"; + } + + // Очистка от бинарных операторов сравнения с только одним операндом (крайние) + $statuses_or = trim(trim(trim($statuses_or), '||')); + + // Инициализация буфера с объёдинёнными буферами c AQL-выражениям "И" и "ИЛИ" + $statuses_merged = (empty($statuses_and) ? '' : "($statuses_and)") . (empty($statuses_or) ? '' : (empty($statuses_and) ? '' : ' || ') . "($statuses_or)"); + + // Инициализация общего буфера с AQL-выражениями + $filters = ''; + + // Объединение фильров в единую строку с AQL-выражениями для инъекции + if (!empty($statuses_merged)) $filters .= empty($filters) ? $statuses_merged : " && ($statuses_merged)"; + if (!empty($polysemantic)) $filters .= empty($filters) ? $polysemantic : " && $polysemantic"; + + // Инициализация данных для генерации HTML-документа с таблицей + $this->view->rows = model::list(before: empty($filters) ? null : "FILTER ($filters)", page: (int) $this->session->buffer[$_SERVER['INTERFACE']]['workers']['page']); + + // Запись в cookie (только таким методом можно записать "hostonly: true") + setcookie( + 'workers_page', + (string) $this->session->buffer[$_SERVER['INTERFACE']]['workers']['page'], + [ + 'expires' => strtotime('+1 hour'), + 'path' => '/', + 'secure' => true, + 'httponly' => false, + 'samesite' => 'strict' + ] + ); + + // Запись в глобальную переменную шаблонизатора обрабатываемой страницы + $this->view->page = $parameters['page']; + + // Инициализация блока + return $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'workers.html'); + } + + // Возврат (провал) + return null; + } + + /** + * Прочитать данные сотрудников для + * + * @param array $parameters Параметры запроса + */ + public function datalist(array $parameters = []): ?string + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) { + // Авторизован аккаунт оператора или магазина + + // Инициализация данных сотрудников + $this->view->workers = model::read(filter: 'd.status == "active"', amount: 10000, return: '{ id: d.id, name: d.name }'); + + // Универсализация + if ($this->view->workers instanceof _document) $this->view->workers = [$this->view->workers]; + + // Возврат (успех) + return $this->view->render(DIRECTORY_SEPARATOR . 'lists' . DIRECTORY_SEPARATOR . 'workers.html'); + } + + // Возврат (провал) + return null; + } +} diff --git a/mirzaev/ebala/system/models/account.php b/mirzaev/ebala/system/models/account.php new file mode 100755 index 0000000..7c53d17 --- /dev/null +++ b/mirzaev/ebala/system/models/account.php @@ -0,0 +1,491 @@ + + */ +final class account extends core +{ + use instance, status; + + /** + * Коллекция + */ + final public const COLLECTION = 'account'; + + /** + * Инстанция документа в базе данных + */ + protected readonly _document $document; + + /** + * Конструктор + * + * @param ?session $session Инстанция сессии + * @param ?string $authenticate Аутентифицировать аккаунт? Если да, то какой категории? ([worker|operator|market] из $_SERVER['INTERFACE']) + * @param array &$errors Реестр ошибок + * + * @return static Инстанция аккаунта + */ + public function __construct(?session $session = null, ?string $authenticate = null, array &$errors = []) + { + try { + if (isset($session)) { + // Получена инстанция сессии + + if ($account = $session->account()) { + // Найден связанный с сессией аккаунт + + // Инициализация инстанции документа аккаунта в базе данных + $this->document = $account->document; + + // Связь сессии с аккаунтом + $session->connect($this, $errors); + + return $this; + } else { + // Не найден связанный с сессией аккаунт + if ( + match ($authenticate) { + 'worker', 'operator', 'market', 'administrator' => true, + default => false + } + ) { + // Запрошена аутентификация + + if (!empty($session->buffer['worker']['entry']['number'])) { + // Найден номер сотрудника в буфере сессии + + if (!empty($session->buffer['worker']['entry']['password'])) { + // Найден пароль в буфере сессии + + if (($account = self::read('d.number == ' . $session->buffer['worker']['entry']['number'], amount: 1, errors: $errors)) instanceof _document) { + // Найден аккаунт сотрудника (игнорируются ошибки) + + if (sodium_crypto_pwhash_str_verify($account->password, $session->buffer['worker']['entry']['password'])) { + // Аутентифицирован аккаунт (прошёл проверку пароль) + + // Инициализация инстанции документа аккаунта в базе данных + $this->document = $account; + + // Связь сессии с аккаунтом + $session->connect($this, $errors); + + // Удаление использованных данных из буфера сессии + $session->write(['entry' => ['number' => null, 'password' => null]]); + + // Выход (успех) + return $this; + } else throw new exception('Неправильный пароль'); + + throw new exception('Неизвестная ошибка на этапе проверки пароля'); + } else if ($worker = static::worker($session->buffer['worker']['entry']['number'])) { + // Найден сотрудник + + if (self::create([ + 'number' => $session->buffer['worker']['entry']['number'], + 'password' => sodium_crypto_pwhash_str( + $session->buffer['worker']['entry']['password'], + SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE, + SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE + ), + 'status' => 'active', + 'type' => 'worker' + ], $errors)) { + // Зарегистрирован аккаунт + + if (($account = self::read('d.number == "' . $session->buffer['worker']['entry']['number'] . '"', amount: 1, errors: $errors)) instanceof _document) { + // Найден аккаунт + + // Инициализация инстанции документа аккаунта в базе данных + $this->document = $account; + + // Связь сессии с аккаунтом + $session->connect($this, $errors); + + // Связь аккаунта с сотрудником + $this->connect($worker, $errors); + + // Удаление использованных данных из буфера сессии + $session->write(['entry' => ['number' => null, 'password' => null]]); + + // Выход (успех) + return $this; + } else throw new exception('Не удалось аутентифицировать аккаунт после его регистрации'); + } else throw new exception('Не удалось зарегистрировать аккаунт'); + } else throw new exception('Не найден аккаунт'); + } else throw new exception('Не найден пароль в буфере сессии'); + } else if (!empty($session->buffer['operator']['entry']['_key'])) { + // Найден идентификатор оператора в буфере сессии + + if (!empty($session->buffer['operator']['entry']['password'])) { + // Найден пароль в буфере сессии + + if (($account = self::read('d._key == "' . $session->buffer['operator']['entry']['_key'] . '"', amount: 1)) instanceof _document) { + // Найден аккаунт оператора (игнорируются ошибки) + + if (sodium_crypto_pwhash_str_verify($account->password, $session->buffer['operator']['entry']['password'])) { + // Аутентифицирован аккаунт (прошёл проверку пароль) + + // Инициализация инстанции документа аккаунта в базе данных + $this->document = $account; + + // Связь сессии с аккаунтом + $session->connect($this, $errors); + + // Удаление использованных данных из буфера сессии + $session->write(['entry' => ['_key' => null, 'password' => null]]); + + // Выход (успех) + return $this; + } else throw new exception('Неправильный пароль'); + + throw new exception('Неизвестная ошибка на этапе проверки пароля'); + } + } else throw new exception('Не найден пароль в буфере сессии'); + } else if (!empty($session->buffer['market']['entry'])) { + // Найден идентификатор магазина в буфере сессии + + if (!empty($session->buffer['market']['entry']['password'])) { + // Найден пароль в буфере сессии + + if (($market = market::read('d.id == "' . $session->buffer['market']['entry']['id'] . '"', amount: 1)) instanceof _document) { + // Найден магазин (игнорируются ошибки) + + if (($account = market::account($market->getId())) instanceof _document) { + // Найден аккаунт (игнорируются ошибки) + + if (sodium_crypto_pwhash_str_verify($account->password, $session->buffer['market']['entry']['password'])) { + // Аутентифицирован аккаунт (прошёл проверку пароль) + + // Инициализация инстанции документа аккаунта в базе данных + $this->document = $account; + + // Связь сессии с аккаунтом + $session->connect($this, $errors); + + // Удаление использованных данных из буфера сессии + $session->write(['entry' => ['id' => null, 'password' => null]]); + + // Выход (успех) + return $this; + } else throw new exception('Неправильный пароль'); + + throw new exception('Неизвестная ошибка на этапе проверки пароля'); + } + } + } else throw new exception('Не найден пароль в буфере сессии'); + } else if (!empty($session->buffer['administrator']['entry'])) { + // Найден идентификатор администратора в буфере сессии + + if (!empty($session->buffer['administrator']['entry']['password'])) { + // Найден пароль в буфере сессии + + if (($account = self::read('d._key == "' . $session->buffer['administrator']['entry']['_key'] . '"', amount: 1)) instanceof _document) { + // Найден аккаунт администратора (игнорируются ошибки) + + if (sodium_crypto_pwhash_str_verify($account->password, $session->buffer['administrator']['entry']['password'])) { + // Аутентифицирован аккаунт (прошёл проверку пароль) + + // Инициализация инстанции документа аккаунта в базе данных + $this->document = $account; + + // Связь сессии с аккаунтом + $session->connect($this, $errors); + + // Удаление использованных данных из буфера сессии + $session->write(['entry' => ['_key' => null, 'password' => null]]); + + // Выход (успех) + return $this; + } else throw new exception('Неправильный пароль'); + + throw new exception('Неизвестная ошибка на этапе проверки пароля'); + } + } else throw new exception('Не найден пароль в буфере сессии'); + } else throw new exception('Не найдены данные первичной идентификации в буфере сессии'); + } + } + } else throw new exception('Не найдена сессия'); + } catch (exception $e) { + // Запись в реестр ошибок + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + } + + /** + * Найти сотрудника + * + * @param int|string $number Номер + * @param array &$errors Реестр ошибок + * + * @return ?_document Инстанция документа сотрудника в базе данных, если найдена + */ + public static function worker(int|string $number, array &$errors = []): ?_document + { + try { + if (collection::init(static::$arangodb->session, worker::COLLECTION)) { + // Инициализирована коллекция + + $worker = collection::search( + static::$arangodb->session, + sprintf( + <<<'AQL' + FOR d IN %s + FILTER d.phone == '%s' + SORT d.created DESC + RETURN d + AQL, + worker::COLLECTION, + $number + ) + ); + + // Возврат (успех) + return $worker instanceof _document ? $worker : throw new exception('Не удалось найти инстанцию сотрудника в базе данных'); + } else throw new exception('Не удалось инициализировать коллекцию'); + } catch (exception $e) { + // Запись в реестр ошибок + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + return null; + } + + /** + * Найти связанный магазин + * + * @param string $_id Идентификатор аккаунта связанного с магазином + * @param array &$errors Реестр ошибок + * + * @return _document|null Инстанция документа магазина в базе данных, если найдена + */ + public static function market(string $_id, array &$errors = []): ?_document + { + try { + if ( + collection::init(static::$arangodb->session, static::COLLECTION) + && collection::init(static::$arangodb->session, market::COLLECTION) + && collection::init(static::$arangodb->session, static::COLLECTION . '_edge_market', true) + ) { + // Инициализированы коллекции + + return collection::search( + static::$arangodb->session, + sprintf( + <<<'AQL' + FOR d IN %s + LET e = ( + FOR e IN %s + FILTER e._from == '%s' + SORT e._key DESC + LIMIT 1 + RETURN e + ) + FILTER d._id == e[0]._to && d.status == 'active' + SORT d.created DESC + LIMIT 1 + RETURN d + AQL, + market::COLLECTION, + static::COLLECTION . '_edge_market', + $_id + ) + ); + } else throw new exception('Не удалось инициализировать коллекции'); + } catch (exception $e) { + // Запись в реестр ошибок + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + return null; + } + + /** + * Инициализировать связь аккаунта с сотрудником + * + * Ищет связь аккаунта с сотрудником, если не находит, то создаёт её + * + * @param _document $worker Инстанция документа в базе данных сотрудника + * @param array &$errors Реестр ошибок + * + * @return bool Связан аккаунт с сотрудником? + */ + public function connect(_document $worker, array &$errors = []): bool + { + try { + if ( + collection::init($this::$arangodb->session, 'worker') + && collection::init($this::$arangodb->session, self::COLLECTION) + && collection::init($this::$arangodb->session, self::COLLECTION . '_edge_worker', true) + ) { + // Инициализированы коллекции + + if ( + collection::search($this::$arangodb->session, sprintf( + <<document->getId(), + $worker->getId() + )) instanceof _document + || document::write($this::$arangodb->session, self::COLLECTION . '_edge_worker', [ + '_from' => $this->document->getId(), + '_to' => $worker->getId() + ]) + ) { + // Найдено, либо создано ребро: account -> worker + + return true; + } else throw new exception('Не удалось создать ребро: account -> worker'); + } else throw new exception('Не удалось инициализировать коллекцию'); + } catch (exception $e) { + // Запись в реестр ошибок + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + return false; + } + /** + * Создать + * + * @param array $data Данные аккаунта + * @param array &$errors Реестр ошибок + * + * @return bool Создан аккаунт? + */ + public static function create(array $data = [], array &$errors = []): bool + { + try { + if (collection::init(static::$arangodb->session, self::COLLECTION)) + if (document::write(static::$arangodb->session, self::COLLECTION, $data)) return true; + else throw new exception('Не удалось создать аккаунт'); + else throw new exception('Не удалось инициализировать коллекцию'); + } catch (exception $e) { + // Запись в реестр ошибок + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + return false; + } + + /** + * Записать + * + * Записывает свойство в инстанцию документа аккаунта из базы данных + * + * @param string $name Название + * @param mixed $value Содержимое + * + * @return void + */ + public function __set(string $name, mixed $value = null): void + { + $this->document->{$name} = $value; + } + + /** + * Прочитать + * + * Читает свойство из инстанции документа аккаунта из базы данных + * + * @param string $name Название + * + * @return mixed Данные свойства инстанции аккаунта или инстанции документа аккаунта из базы данных + */ + public function __get(string $name): mixed + { + return $this->document->{$name}; + } + + /** + * Проверить инициализированность + * + * Проверяет инициализированность свойства в инстанции документа аккаунта из базы данных + * + * @param string $name Название + * + * @return bool Свойство инициализировано? + */ + public function __isset(string $name): bool + { + return isset($this->document->{$name}); + } + + /** + * Удалить + * + * Деинициализировать свойство в инстанции документа аккаунта из базы данных + * + * @param string $name Название + * + * @return void + */ + public function __unset(string $name): void + { + unset($this->document->{$name}); + } + + /** + * Выполнить метод + * + * Выполнить метод в инстанции документа аккаунта из базы данных + * + * @param string $name Название + * @param array $arguments Аргументы + */ + public function __call(string $name, array $arguments = []) + { + if (method_exists($this->document, $name)) return $this->document->{$name}($arguments); + } +} diff --git a/mirzaev/ebala/system/models/core.php b/mirzaev/ebala/system/models/core.php new file mode 100755 index 0000000..9f6fcb6 --- /dev/null +++ b/mirzaev/ebala/system/models/core.php @@ -0,0 +1,275 @@ + + */ +class core extends model +{ + /** + * Постфикс + */ + final public const POSTFIX = ''; + + /** + * Путь до файла с настройками подключения к базе данных ArangoDB + */ + final public const ARANGODB = '../settings/arangodb.php'; + + /** + * Соединение с базой данных ArangoDB + */ + protected static arangodb $arangodb; + + /** + * Коллекция + */ + public const COLLECTION = 'THIS_COLLECTION_SHOULD_NOT_EXIST'; + + /** + * Конструктор + * + * @param bool $initialize Инициализировать модель? + * @param ?arangodb $arangodb Инстанция соединения с базой данных ArangoDB + */ + public function __construct(bool $initialize = true, ?arangodb $arangodb = null) + { + parent::__construct($initialize); + + if ($initialize) { + // Запрошена инициализация + + if (isset($arangodb)) { + // Получена инстанция соединения с базой данных + + // Запись и инициализация соединения с базой данных + $this->__set('arangodb', $arangodb); + } else { + // Не получена инстанция соединения с базой данных + + // Инициализация соединения с базой данных по умолчанию + $this->__get('arangodb'); + } + } + } + + /** + * Read from ArangoDB + * + * @param string $filter Выражения для фильтрации на языке AQL + * @param string $sort Выражение для сортировки на языке AQL + * @param int $amount Количество документов для выборки + * @param int $page Страница + * @param string $return Выражение описываемое возвращаемые данные на языке AQL + * @param array &$errors Реестр ошибок + * + * @return _document|array|null Массив инстанций документов в базе данных, если найдены + */ + public static function read( + string $filter = '', + string $sort = 'd.created DESC', + int $amount = 1, + int $page = 1, + string $return = 'd', + array &$errors = [] + ): _document|array|null { + try { + if (collection::init(static::$arangodb->session, static::COLLECTION)) { + // Инициализирована коллекция + + // Exit (success) + return collection::search( + static::$arangodb->session, + sprintf( + <<<'AQL' + FOR d IN %s + %s + %s + LIMIT %d, %d + RETURN %s + AQL, + static::COLLECTION, + empty($filter) ? '' : "FILTER $filter", + empty($sort) ? '' : "SORT $sort", + --$page <= 0 ? 0 : $amount * $page, + $amount, + $return + ) + ); + } else throw new exception('Не удалось инициализировать коллекцию'); + } catch (exception $e) { + // Запись в реестр ошибок + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } + + /** + * Delete from ArangoDB + * + * @param _document $instance Instance of the document in ArangoDB + * @param array &$errors Реестр ошибок + * + * @return bool Deleted without errors? + */ + public static function delete(_document $instance, array &$errors = []): bool + { + try { + if (collection::init(static::$arangodb->session, static::COLLECTION)) { + // Инициализирована коллекция + + return (new _document_handler(static::$arangodb->session))->remove($instance); + } else throw new exception('Не удалось инициализировать коллекцию'); + } catch (exception $e) { + // Запись в реестр ошибок + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return false; + } + + /** + * Обновить инстанцию в базе данных + * + * Да поебать мне + * + * @param _document $instance Инстанция документа ArangoDB + * + * @return bool Записано в базу данных? + */ + public static function update(_document $instance): bool + { + return document::update(static::$arangodb->session, $instance); + } + + /** + * Записать свойство + * + * @param string $name Название + * @param mixed $value Значение + */ + public function __set(string $name, mixed $value = null): void + { + match ($name) { + 'arangodb' => (function () use ($value) { + if ($this->__isset('arangodb')) { + // Свойство уже было инициализировано + + // Выброс исключения (неудача) + throw new exception('Запрещено реинициализировать соединение с базой данных ArangoDB ($this::$arangodb)', 500); + } else { + // Свойство ещё не было инициализировано + + if ($value instanceof arangodb) { + // Передано подходящее значение + + // Запись свойства (успех) + self::$arangodb = $value; + } else { + // Передано неподходящее значение + + // Выброс исключения (неудача) + throw new exception('Соединение с базой данных ArangoDB ($this::$arangodb) должно быть инстанцией mirzaev\arangodb\connection', 500); + } + } + })(), + default => parent::__set($name, $value) + }; + } + + /** + * Прочитать свойство + * + * @param string $name Название + * + * @return mixed Содержимое + */ + public function __get(string $name): mixed + { + return match ($name) { + 'arangodb' => (function () { + try { + if (!$this->__isset('arangodb')) { + // Свойство не инициализировано + + // Инициализация значения по умолчанию исходя из настроек + $this->__set('arangodb', new arangodb(require static::ARANGODB)); + } + + return self::$arangodb; + } catch (exception) { + return null; + } + })(), + default => parent::__get($name) + }; + } + + /** + * Проверить свойство на инициализированность + * + * @param string $name Название + */ + public function __isset(string $name): bool + { + return parent::__isset($name); + } + + /** + * Удалить свойство + * + * @param string $name Название + */ + public function __unset(string $name): void + { + parent::__unset($name); + } + + /** + * Статический вызов + * + * @param string $name Название + * @param array $arguments Параметры + */ + public static function __callStatic(string $name, array $arguments): mixed + { + match ($name) { + 'arangodb' => (new static)->__get('arangodb'), + default => throw new exception("Не найдено свойство или функция: $name", 500) + }; + } +} diff --git a/mirzaev/ebala/system/models/market.php b/mirzaev/ebala/system/models/market.php new file mode 100755 index 0000000..9d2ec46 --- /dev/null +++ b/mirzaev/ebala/system/models/market.php @@ -0,0 +1,245 @@ + + */ +final class market extends core +{ + use instance, status; + + /** + * Коллекция + * + * @todo Исправить "markets" на "market" + */ + /* final public const COLLECTION = 'market'; */ + final public const COLLECTION = 'markets'; + + /** + * Инстанция документа в базе данных + */ + protected readonly _document $document; + + /** + * Конструктор + * + * @param _document $instance Инстанция в базе данных + * @param array &$errors Реестр ошибок + * + * @return static Инстанция магазина + */ + public function __construct(_document $instance, array &$errors = []) + { + try { + // Инициализация инстанции документа магазина в базе данных + $this->document = $instance; + } catch (exception $e) { + // Запись в реестр ошибок + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + } + + /** + * Read markets from ArangoDB + * + * @param ?string $before Injection of AQL-code before search of market and market + * @param int $amount Amount of markets + * @param int $page Offset by amount + * @param array $errors Errors registry + * + * @return array Instances from ArangoDB + */ + public static function list( + ?string $before = '', + int $amount = 100, + int $page = 1, + string $sort = 'market.created DESC', + array &$errors = [] + ): array { + try { + if (collection::init(static::$arangodb->session, self::COLLECTION)) { + // Инициализирована коллекция + + // Search the session data in ArangoDB + $markets = collection::search(static::$arangodb->session, sprintf( + << $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return []; + } + + /** + * Найти связанный аккаунт + * + * @param string $_id Идентификатор магазина для которого будет искаться аккаунт + * @param array &$errors Реестр ошибок + * + * @return _document|array|null Массив инстанций документов в базе данных, если найдены + */ + public static function account(string $_id, array &$errors = []): _document|array|null + { + try { + if ( + collection::init(static::$arangodb->session, static::COLLECTION) + && collection::init(static::$arangodb->session, account::COLLECTION) + && collection::init(static::$arangodb->session, account::COLLECTION . '_edge_market', true) + ) { + // Инициализированы коллекции + + return collection::search( + static::$arangodb->session, + sprintf( + <<<'AQL' + FOR d IN %s + LET e = ( + FOR e IN %s + FILTER e._to == "%s" + SORT e.created DESC + LIMIT 1 + RETURN e + ) + FILTER d._id == e[0]._from && d.status == "active" + SORT d.created DESC + LIMIT 1 + RETURN d + AQL, + account::COLLECTION, + account::COLLECTION . '_edge_market', + $_id + ) + ); + } else throw new exception('Не удалось инициализировать коллекции'); + } catch (exception $e) { + // Запись в реестр ошибок + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + return null; + } + + /** + * Записать + * + * Записывает свойство в инстанцию документа аккаунта из базы данных + * + * @param string $name Название + * @param mixed $value Содержимое + * + * @return void + */ + public function __set(string $name, mixed $value = null): void + { + $this->document->{$name} = $value; + } + + /** + * Прочитать + * + * Читает свойство из инстанции документа аккаунта из базы данных + * + * @param string $name Название + * + * @return mixed Данные свойства инстанции аккаунта или инстанции документа аккаунта из базы данных + */ + public function __get(string $name): mixed + { + return $this->document->{$name}; + } + + /** + * Проверить инициализированность + * + * Проверяет инициализированность свойства в инстанции документа аккаунта из базы данных + * + * @param string $name Название + * + * @return bool Свойство инициализировано? + */ + public function __isset(string $name): bool + { + return isset($this->document->{$name}); + } + + /** + * Удалить + * + * Деинициализировать свойство в инстанции документа аккаунта из базы данных + * + * @param string $name Название + * + * @return void + */ + public function __unset(string $name): void + { + unset($this->document->{$name}); + } + + /** + * Выполнить метод + * + * Выполнить метод в инстанции документа аккаунта из базы данных + * + * @param string $name Название + * @param array $arguments Аргументы + */ + public function __call(string $name, array $arguments = []) + { + if (method_exists($this->document, $name)) return $this->document->{$name}($arguments); + } +} diff --git a/mirzaev/ebala/system/models/session.php b/mirzaev/ebala/system/models/session.php new file mode 100755 index 0000000..0089bb8 --- /dev/null +++ b/mirzaev/ebala/system/models/session.php @@ -0,0 +1,413 @@ + + */ +final class session extends core +{ + use status; + + /** + * Collection name in ArangoDB + */ + final public const COLLECTION = 'session'; + + /** + * Инстанция документа в базе данных + */ + protected readonly _document $document; + + /** + * Конструктор + * + * Инициализация сессии и запись в свойство $this->document + * + * @param ?string $hash Хеш сессии в базе данных + * @param ?int $expires Дата окончания работы сессии (используется при создании новой сессии) + * @param array &$errors Реестр ошибок + * + * @return static Инстанция сессии + */ + public function __construct(?string $hash = null, ?int $expires = null, array &$errors = []) + { + try { + if (collection::init(static::$arangodb->session, self::COLLECTION)) { + // Инициализирована коллекция + + // Инициализация идентификации по IP (через прокси или напрямую) + /* $ip = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? 'd[\'x-forwarded-for\'] == \'' . $_SERVER['HTTP_X_FORWARDED_FOR'] . '\'' : 'd.ip == \'' . $_SERVER['REMOTE_ADDR'] . '\''; + + if ( + isset($hash) + && $session = collection::search($this::$arangodb->session, sprintf( + << %d && d.status == 'active' + RETURN d + AQL, + self::COLLECTION, + $ip, + $hash, + time() + )) + ) { + // Received session hash and session found + + // Запись в свойство + $this->document = $session; + } */ + + if ( + isset($hash) + && $session = collection::search($this::$arangodb->session, sprintf( + << %d && d.status == 'active' + RETURN d + AQL, + self::COLLECTION, + $hash, + time() + )) + ) { + // Received session hash and session found + + // Запись в свойство + $this->document = $session; + } else { + // Не найдена сессия + + // Запись сессии в базу данных + $_id = document::write($this::$arangodb->session, self::COLLECTION, [ + 'status' => 'active', + 'expires' => $expires ?? time() + 604800, + 'ip' => $_SERVER['REMOTE_ADDR'], + 'x-forwarded-for' => $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null, + 'referer' => $_SERVER['HTTP_REFERER'] ?? null, + 'useragent' => $_SERVER['HTTP_USER_AGENT'] ?? null + ]); + + if ($session = collection::search($this::$arangodb->session, sprintf( + << %d && d.status == 'active' + RETURN d + AQL, + self::COLLECTION, + time() + ))) { + // Найдена только что созданная сессия + + // Запись хеша + $session->hash = sodium_bin2hex(sodium_crypto_generichash($_id)); + + if (document::update($this::$arangodb->session, $session)) { + // Записано обновление + + // Запись в свойство + $this->document = $session; + } else throw new exception('Не удалось записать данные сессии'); + } else throw new exception('Не удалось создать или найти созданную сессию'); + } + } else throw new exception('Не удалось инициализировать коллекцию'); + } catch (exception $e) { + // Запись в реестр ошибок + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + } + + public function search(string $hash, array &$errors = []): bool + { + try { + search_arangodb: + + // Search the session data in ArangoDB + $_document = collection::search(static::$arangodb->session, sprintf( + << %d && d.status == 'active' + RETURN d + AQL, + self::COLLECTION, + time() + )); + + if ($_document instanceof _document) { + // The instance found + + // Write the session data to the property + $this->document = $_document; + + // Exit (success) + return true; + } + } 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; + } + + + public function __destruct() + { + // Закрыть сессию + } + + /** + * Инициализировать связь сессии с аккаунтом + * + * Ищет связь сессии с аккаунтом, если не находит, то создаёт её + * + * @param account $account Инстанция аккаунта + * @param array &$errors Реестр ошибок + * + * @return bool Связана сессия с аккаунтом? + */ + public function connect(account $account, array &$errors = []): bool + { + try { + if ( + collection::init($this::$arangodb->session, self::COLLECTION) + && collection::init($this::$arangodb->session, account::COLLECTION) + && collection::init($this::$arangodb->session, self::COLLECTION . '_edge_' . account::COLLECTION, true) + ) { + // Инициализированы коллекции + + if ( + collection::search($this::$arangodb->session, sprintf( + <<document->getId(), + $account->getId() + )) instanceof _document + || document::write($this::$arangodb->session, self::COLLECTION . '_edge_' . account::COLLECTION, [ + '_from' => $this->document->getId(), + '_to' => $account->getId() + ]) + ) { + // Найдено, либо создано ребро: 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 Инстанция аккаунта, если удалось найти + */ + public function account(array &$errors = []): ?account + { + try { + if ( + collection::init($this::$arangodb->session, self::COLLECTION) + && collection::init($this::$arangodb->session, account::COLLECTION) + && collection::init($this::$arangodb->session, self::COLLECTION . '_edge_' . account::COLLECTION, true) + ) { + // Инициализированы коллекции + + // Инициализация инстанции аккаунта + $account = new account; + + // Поиск инстанции аккаунта в базе данных + $instance = $account->instance(collection::search($this::$arangodb->session, sprintf( + <<getId() + ))); + + // Возврат (успех) + return $instance instanceof _document ? $account : throw new exception('Не удалось найти инстанцию аккаунта в базе данных'); + } else throw new exception('Не удалось инициализировать коллекцию'); + } catch (exception $e) { + // Запись в реестр ошибок + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + return null; + } + + /** + * Записать в буфер сессии + * + * @param array $data Данные для записи + * @param array &$errors Реестр ошибок + * + * @return bool Записаны данные в буфер сессии? + */ + public function write(array $data, array &$errors = []): bool + { + try { + if (collection::init($this::$arangodb->session, self::COLLECTION)) { + // Инициализирована коллекция + + // Проверка инициализированности инстанции документа из базы данных + if (!isset($this->document)) throw new exception('Не инициализирована инстанция документа из базы данных'); + + // Запись параметров в инстанцию документа из базы данных + $this->document->buffer = array_replace_recursive( + $this->document->buffer ?? [], + [$_SERVER['INTERFACE'] => array_replace_recursive($this->document->buffer[$_SERVER['INTERFACE']] ?? [], $data)] + ); + + // Запись в базу данных и возврат (успех) + return document::update($this::$arangodb->session, $this->document) ? true : throw new exception('Не удалось записать данные в буфер сессии'); + } else throw new exception('Не удалось инициализировать коллекцию'); + } catch (exception $e) { + // Запись в реестр ошибок + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + return false; + } + + /** + * Записать + * + * Записывает свойство в инстанцию документа сессии из базы данных + * + * @param string $name Название + * @param mixed $value Содержимое + * + * @return void + */ + public function __set(string $name, mixed $value = null): void + { + $this->document->{$name} = $value; + } + + /** + * Прочитать + * + * Читает свойство из инстанции документа сессии из базы данных + * + * @param string $name Название + * + * @return mixed Данные свойства инстанции сессии или инстанции документа сессии из базы данных + */ + public function __get(string $name): mixed + { + return match ($name) { + 'arangodb' => $this::$arangodb, + default => $this->document->{$name} + }; + } + + /** + * Проверить инициализированность + * + * Проверяет инициализированность свойства в инстанции документа сессии из базы данных + * + * @param string $name Название + * + * @return bool Свойство инициализировано? + */ + public function __isset(string $name): bool + { + return isset($this->document->{$name}); + } + + /** + * Удалить + * + * Деинициализировать свойство в инстанции документа сессии из базы данных + * + * @param string $name Название + * + * @return void + */ + public function __unset(string $name): void + { + unset($this->document->{$name}); + } + + /** + * Выполнить метод + * + * Выполнить метод в инстанции документа сессии из базы данных + * + * @param string $name Название + * @param array $arguments Аргументы + */ + public function __call(string $name, array $arguments = []) + { + if (method_exists($this->document, $name)) return $this->document->{$name}($arguments); + } +} diff --git a/mirzaev/ebala/system/models/task.php b/mirzaev/ebala/system/models/task.php new file mode 100755 index 0000000..27fed52 --- /dev/null +++ b/mirzaev/ebala/system/models/task.php @@ -0,0 +1,175 @@ + + */ +final class task extends core +{ + use status; + + /** + * Collection name in ArangoDB + */ + final public const COLLECTION = 'task'; + + /** + * Инстанция документа в базе данных + */ + protected readonly _document $document; + + /** + * Create task in ArangoDB + * + * @param ?string $date + * @param ?string $worker + * @param ?string $work + * @param ?string $start + * @param ?string $end + * @param ?string $market + * @param bool $confirmed + * @param bool $published + * @param bool $hided + * @param bool $problematic + * @param bool $completed + * @param array $errors + * + * @return ?string Identificator of instance of ArangoDB + */ + public static function create( + ?string $date = null, + ?string $worker = null, + ?string $work = null, + ?string $start = null, + ?string $end = null, + ?string $market = null, + bool $confirmed = false, + bool $published = false, + bool $hided = false, + bool $problematic = false, + bool $completed = false, + array &$errors = [] + ): ?string { + try { + if ( + collection::init(static::$arangodb->session, self::COLLECTION) + && collection::init(static::$arangodb->session, worker::COLLECTION) + && collection::init(static::$arangodb->session, market::COLLECTION) + ) { + // Инициализированы коллекции + + // Запись документа в базу данны и возврат (успех) + return document::write(static::$arangodb->session, self::COLLECTION, [ + 'date' => (int) ($date ?? strtotime('+1 day')), + 'worker' => $worker, + 'work' => $work, + 'start' => $start, + 'end' => $end, + 'market' => $market, + 'confirmed' => $confirmed, + 'published' => $published, + 'hided' => $hided, + 'problematic' => $problematic, + 'completed' => $completed, + ]); + } 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() + ]; + } + + // Exit (fail) + return null; + } + + /** + * Read tasks from ArangoDB + * + * @param ?string $before Injection of AQL-code before search of worker and market + * @param ?string $after Injection of AQL-code after search of worker and market + * @param int $amount Amount of tasks + * @param int $page Offset by amount + * @param array $errors Errors registry + * + * @return array Instances from ArangoDB + */ + public static function list( + ?string $before = '', + ?string $after = '', + int $amount = 100, + int $page = 1, + string $sort = 'task.created DESC', + array &$errors = [] + ): array { + try { + if ( + collection::init(static::$arangodb->session, self::COLLECTION) + && collection::init(static::$arangodb->session, worker::COLLECTION) + && collection::init(static::$arangodb->session, market::COLLECTION) + ) { + // Инициализированы коллекции + + // Search the session data in ArangoDB (LET можно заменить на поиск по рёбрам но тут и так сойдёт) + $tasks = collection::search(static::$arangodb->session, sprintf( + << $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return []; + } +} diff --git a/mirzaev/ebala/system/models/traits/instance.php b/mirzaev/ebala/system/models/traits/instance.php new file mode 100755 index 0000000..96dbbfb --- /dev/null +++ b/mirzaev/ebala/system/models/traits/instance.php @@ -0,0 +1,36 @@ + + */ +trait instance +{ + /** + * Инициализация инстанции документа в базе данных + * + * @param ?_document $document Инстанция документа в базе данных для записи + * + * @return ?_document Инстанция документа в базе данных, если инициализирована + */ + public function instance(?_document $document = null): ?_document + { + // Проверка инициализированности и возврат (успех) + if (isset($this->document)) return $this->document; + + // Проверка инстанции документа в базе данных для записи и возврат (провал) + if ($document === null) return null; + + // Запись в свойство и возврат (успех) + return $this->document = $document; + } +} diff --git a/mirzaev/ebala/system/models/traits/status.php b/mirzaev/ebala/system/models/traits/status.php new file mode 100755 index 0000000..89a3349 --- /dev/null +++ b/mirzaev/ebala/system/models/traits/status.php @@ -0,0 +1,44 @@ + + */ +trait status +{ + /** + * Инициализировать статус + * + * @param array &$errors Реестр ошибок + * + * @return ?bool Статус (null - не найден) + */ + public function status(array &$errors = []): ?bool + { + try { + return match ($this->document->status ?? null) { + 'active' => true, + 'inactive' => false, + default => null + }; + } catch (exception $e) { + // Запись в реестр ошибок + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + return null; + } +} diff --git a/mirzaev/ebala/system/models/worker.php b/mirzaev/ebala/system/models/worker.php new file mode 100755 index 0000000..a9853a0 --- /dev/null +++ b/mirzaev/ebala/system/models/worker.php @@ -0,0 +1,189 @@ + + */ +final class worker extends core +{ + use instance, status; + + /** + * Коллекция + */ + final public const COLLECTION = 'worker'; + + /** + * Инстанция документа в базе данных + */ + protected readonly _document $document; + + /** + * Конструктор + * + * @param _document $instance Инстанция в базе данных + * @param array &$errors Реестр ошибок + * + * @return static Инстанция магазина + */ + public function __construct(_document $instance, array &$errors = []) + { + try { + // Инициализация инстанции документа магазина в базе данных + $this->document = $instance; + } catch (exception $e) { + // Запись в реестр ошибок + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + } + + /** + * Read workers from ArangoDB + * + * @param ?string $before Injection of AQL-code before search of worker and market + * @param int $amount Amount of workers + * @param int $page Offset by amount + * @param array $errors Errors registry + * + * @return array Instances from ArangoDB + */ + public static function list( + ?string $before = '', + int $amount = 100, + int $page = 1, + string $sort = 'worker.created DESC', + array &$errors = [] + ): array { + try { + if (collection::init(static::$arangodb->session, self::COLLECTION)) { + // Инициализирована коллекция + + // Search the session data in ArangoDB + $workers = collection::search(static::$arangodb->session, sprintf( + << $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return []; + } + + + /** + * Записать + * + * Записывает свойство в инстанцию документа аккаунта из базы данных + * + * @param string $name Название + * @param mixed $value Содержимое + * + * @return void + */ + public function __set(string $name, mixed $value = null): void + { + $this->document->{$name} = $value; + } + + /** + * Прочитать + * + * Читает свойство из инстанции документа аккаунта из базы данных + * + * @param string $name Название + * + * @return mixed Данные свойства инстанции аккаунта или инстанции документа аккаунта из базы данных + */ + public function __get(string $name): mixed + { + return $this->document->{$name}; + } + + /** + * Проверить инициализированность + * + * Проверяет инициализированность свойства в инстанции документа аккаунта из базы данных + * + * @param string $name Название + * + * @return bool Свойство инициализировано? + */ + public function __isset(string $name): bool + { + return isset($this->document->{$name}); + } + + /** + * Удалить + * + * Деинициализировать свойство в инстанции документа аккаунта из базы данных + * + * @param string $name Название + * + * @return void + */ + public function __unset(string $name): void + { + unset($this->document->{$name}); + } + + /** + * Выполнить метод + * + * Выполнить метод в инстанции документа аккаунта из базы данных + * + * @param string $name Название + * @param array $arguments Аргументы + */ + public function __call(string $name, array $arguments = []) + { + if (method_exists($this->document, $name)) return $this->document->{$name}($arguments); + } +} diff --git a/mirzaev/ebala/system/public/css/account.css b/mirzaev/ebala/system/public/css/account.css new file mode 100755 index 0000000..e344f4d --- /dev/null +++ b/mirzaev/ebala/system/public/css/account.css @@ -0,0 +1,119 @@ +main { + z-index: 1000; + margin: 20vh auto; + position: relative; + height: unset; + display: flex; + flex-direction: column; + justify-content: unset; + align-items: center; + gap: 10px; +} + +section.panel { + --display: flex; + z-index: 1000; + width: 400px; + position: relative; + display: flex; + flex-direction: column; +} + +div.column > section.panel { + position: unset; +} + +section.panel.medium { + width: 300px; +} + +section.panel.small { + width: 220px; +} + +section.panel#mnemonic { + margin-left: -570px; +} + +section.panel#classic { + margin-left: 570px; +} + +section.panel > section.body > ul { + margin: 0 5%; + padding: 0; + display: flex; + flex-direction: column; + gap: 4px; + list-style: square; +} + +section.panel > section.body > ul > li { + font-size: 0.8rem; + word-break: break-word; + animation-duration: 0.35s; + animation-name: uprise; + animation-fill-mode: forwards; + animation-timing-function: cubic-bezier(0.47, 0, 0.74, 0.71); +} + +section.panel > section.body > dl { + margin: 0; + display: flex; + flex-direction: column; + gap: 4px; +} + +section.panel > section.body > dl > * { + word-break: break-word; + animation-duration: 0.35s; + animation-name: uprise; + animation-fill-mode: forwards; + animation-timing-function: cubic-bezier(0.47, 0, 0.74, 0.71); +} + +section.panel > section.body > dl > dt { + margin-left: 20px; + display: none; + font-size: 0.9rem; + font-weight: bold; +} + +section.panel > section.body > dl > dd { + margin-left: unset; + font-size: 0.8rem; +} + +section.panel > section.header { + z-index: 1000; + height: 50px; + display: flex; + justify-content: center; + align-items: end; + animation-duration: 120s; + border-radius: 3px 3px 0 0; + background-color: var(--background-above); +} + +section.panel > section.header > :is(h1, h2, h3) { + margin-bottom: unset; +} + +section.panel > section.body { + padding: 20px 30px; + gap: 10px; + display: flex; + flex-direction: column; + border-radius: 0 0 3px 3px; + background-color: var(--background-above); +} + +section.panel > section.postscript { + padding: 10px 12px; +} + +section#entry.panel > section.body > form > label > button { + height: 100%; + padding: 0 10px; + border-radius: 0 3px 3px 0; +} diff --git a/mirzaev/ebala/system/public/css/animations.css b/mirzaev/ebala/system/public/css/animations.css new file mode 100755 index 0000000..a810453 --- /dev/null +++ b/mirzaev/ebala/system/public/css/animations.css @@ -0,0 +1,97 @@ +@keyframes input-error { + 0%, + 20% { + background-color: var(--input-error); + } + + 50%, + 100% { + background-color: var(--background-above-1); + } +} + +@keyframes icon-error { + 0%, + 50% { + color: var(--icon-error); + } + + 80%, + 100% { + color: var(--text-inverse-below-1); + } +} + +@keyframes uprise { + 0% { + opacity: 0; + filter: blur(2px); + } + + 100% { + opacity: 1; + filter: blur(0px); + } +} + +@keyframes window-vertical-open { + 0% { + height: 0; + opacity: 0; + } + + 100% { + height: var(--height, inherit); + opacity: var(--opacity, 1); + } +} + +@keyframes window-vertical-close { + 0% { + height: var(--height, inherit); + opacity: var(--opacity, 1); + } + + 100% { + height: 0; + opacity: 0; + } +} + +@keyframes list-triangle-left { + 0% { + left: -18px; + } + + 40%, + 100% { + left: -30px; + } +} + +@keyframes list-triangle-right { + 0% { + right: -18px; + } + + 40%, + 100% { + right: -30px; + } +} + +.animation.window:not(.hidden, .horizontal) { + overflow: hidden; + animation-duration: 0.1s; + animation-name: window-vertical-open; + animation-fill-mode: forwards; + animation-timing-function: ease-in; +} + +.animation.window.hidden:not(.horizontal) { + overflow: hidden; + animation-duration: 0.05s; + animation-name: window-vertical-close; + animation-fill-mode: forwards; + animation-timing-function: ease-out; +} diff --git a/mirzaev/ebala/system/public/css/icons/arrow_right.css b/mirzaev/ebala/system/public/css/icons/arrow_right.css new file mode 100755 index 0000000..546afb6 --- /dev/null +++ b/mirzaev/ebala/system/public/css/icons/arrow_right.css @@ -0,0 +1,33 @@ +i.icon.arrow.right { + box-sizing: border-box; + position: relative; + display: block; + width: 22px; + --height: 22px; + height: var(--height); +} + +i.icon.arrow.right::after, +i.icon.arrow.right::before { + content: ""; + display: block; + box-sizing: border-box; + position: absolute; + right: 3px; +} + +i.icon.arrow.right::after { + width: 8px; + height: 8px; + border-top: 2px solid; + border-right: 2px solid; + transform: rotate(45deg); + bottom: 7px; +} + +i.icon.arrow.right::before { + width: 16px; + height: 2px; + bottom: 10px; + background: currentColor; +} diff --git a/mirzaev/ebala/system/public/css/icons/home.css b/mirzaev/ebala/system/public/css/icons/home.css new file mode 100644 index 0000000..3aba676 --- /dev/null +++ b/mirzaev/ebala/system/public/css/icons/home.css @@ -0,0 +1,50 @@ +i.icon.home { + position: relative; + margin-bottom: -2px; + --width: 18px; + --height: 14px; + width: 18px; + height: 14px; + display: block; + box-sizing: border-box; + border: 2px solid; + border-top: 0; + border-bottom: 0; + border-top-right-radius: 3px; + border-top-left-radius: 3px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + background: linear-gradient(to left, currentColor 5px, transparent 0) + no-repeat 0 bottom/4px 2px, + linear-gradient(to left, currentColor 5px, transparent 0) no-repeat right + bottom/4px 2px; +} +i.icon.home::after, +i.icon.home::before { + position: absolute; + content: ""; + display: block; + box-sizing: border-box; +} +i.icon.home::before { + left: 0; + top: -5px; + width: 14px; + height: 14px; + border-radius: 3px; + transform: rotate(45deg); + border-top: 2px solid; + border-left: 2px solid; + border-top-left-radius: 4px; +} +i.icon.home::after { + left: 3px; + bottom: 0; + width: 8px; + height: 10px; + border: 2px solid; + border-radius: 100px; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom: 0; +} diff --git a/mirzaev/ebala/system/public/css/icons/keyhole.css b/mirzaev/ebala/system/public/css/icons/keyhole.css new file mode 100755 index 0000000..f356ce4 --- /dev/null +++ b/mirzaev/ebala/system/public/css/icons/keyhole.css @@ -0,0 +1,48 @@ +i.icon.keyhole, +i.icon.keyhole::after, +i.icon.keyhole::before { + display: block; + box-sizing: border-box; + border-radius: 20px; +} + +i.icon.keyhole { + --width: 20px; + --height: 20px; + position: relative; + width: 20px; + height: 20px; + border: 2px solid; +} + +i.icon.keyhole::after, +i.icon.keyhole::before { + position: absolute; + content: ""; +} + +i.icon.keyhole::before { + left: 5px; + top: 3px; + width: 6px; + height: 6px; + border: 2px solid; +} + +i.icon.keyhole::after { + left: 7px; + bottom: 3px; + width: 2px; + height: 5px; + background: currentColor; +} + +label > i.icon.keyhole:first-child { + left: 7px; + scale: 0.9; + border: 2.1px solid; +} + +i.icon.keyhole + input { + padding-left: 34px; +} diff --git a/mirzaev/ebala/system/public/css/icons/lock.css b/mirzaev/ebala/system/public/css/icons/lock.css new file mode 100755 index 0000000..2f0707c --- /dev/null +++ b/mirzaev/ebala/system/public/css/icons/lock.css @@ -0,0 +1,28 @@ +i.icon.lock { + --width: 12px; + --height: 11px; + position: relative; + margin-top: -12px; + width: 12px; + height: 11px; + display: block; + box-sizing: border-box; + border: 2px solid; + border-top-right-radius: 50%; + border-top-left-radius: 50%; + border-bottom: transparent; +} + +i.icon.lock::after { + left: -4px; + top: 9px; + position: absolute; + width: 16px; + height: 10px; + display: block; + box-sizing: border-box; + content: ""; + box-shadow: 0 0 0 2px; + border-radius: 2px; + border: 2px solid transparent; +} diff --git a/mirzaev/ebala/system/public/css/icons/mail.css b/mirzaev/ebala/system/public/css/icons/mail.css new file mode 100755 index 0000000..a402a67 --- /dev/null +++ b/mirzaev/ebala/system/public/css/icons/mail.css @@ -0,0 +1,30 @@ +i.icon.mail, +i.icon.mail::after { + --width: 18px; + --height: 14px; + height: 14px; + display: block; + box-sizing: border-box; + border: 2px solid; +} + +i.icon.mail { + position: relative; + width: 18px; + overflow: hidden; + border-radius: 2px; +} + +i.icon.mail::after { + position: absolute; + left: 0; + bottom: 3px; + width: 14px; + transform: rotate(-45deg); + content: ""; + border-radius: 3px; +} + +i.icon.mail + input { + padding-left: 36px; +} diff --git a/mirzaev/ebala/system/public/css/icons/nametag.css b/mirzaev/ebala/system/public/css/icons/nametag.css new file mode 100755 index 0000000..8366a37 --- /dev/null +++ b/mirzaev/ebala/system/public/css/icons/nametag.css @@ -0,0 +1,30 @@ +i.icon.nametag { + position: relative; + --width: 6px; + --height: 6px; + width: 6px; + height: 6px; + display: block; + box-sizing: border-box; + border: 2px solid; +} + +i.icon.nametag::before { + left: -5px; + top: -5px; + position: absolute; + width: 12px; + height: 12px; + display: block; + box-sizing: border-box; + content: ""; + box-shadow: -5px -5px 0 -3px, 5px 5px 0 -3px, 5px -5px 0 -3px, -5px 5px 0 -3px; +} + +label > i.icon.nametag:first-of-type { + left: 13px; +} + +i.icon.nametag + input { + padding-left: 32px; +} diff --git a/mirzaev/ebala/system/public/css/icons/search.css b/mirzaev/ebala/system/public/css/icons/search.css new file mode 100755 index 0000000..455b2f6 --- /dev/null +++ b/mirzaev/ebala/system/public/css/icons/search.css @@ -0,0 +1,37 @@ +i.icon.search { + --width: 16px; + --height: 16px; + margin-left: -4px; + margin-top: -4px; + position: absolute; + width: 16px; + height: 16px; + display: block; + box-sizing: border-box; + transform: scale(var(--ggs, 1)); + border-radius: 100%; + border: 2px solid; +} + +i.icon.search::after { + content: ""; + left: 12px; + top: 10px; + position: absolute; + width: 2px; + height: 8px; + display: block; + box-sizing: border-box; + border-radius: 3px; + transform: rotate(-45deg); + background: currentColor; +} + +label > i.icon.search:first-of-type { + left: 18px; + top: 12px; +} + +i.icon.search + input { + padding-left: 50px !important; +} diff --git a/mirzaev/ebala/system/public/css/icons/shopping_cart.css b/mirzaev/ebala/system/public/css/icons/shopping_cart.css new file mode 100644 index 0000000..33029bf --- /dev/null +++ b/mirzaev/ebala/system/public/css/icons/shopping_cart.css @@ -0,0 +1,38 @@ +i.icon.shopping.cart { + position: relative; + --width: 20px; + --height: 21px; + width: 20px; + height: 21px; + display: block; + box-sizing: border-box; + background: linear-gradient(to left, currentColor 12px, transparent 0) + no-repeat -1px 6px/18px 2px, + linear-gradient(to left, currentColor 12px, transparent 0) no-repeat 6px + 14px/11px 2px, + linear-gradient(to left, currentColor 12px, transparent 0) no-repeat 0 2px/4px + 2px, + radial-gradient(circle, currentColor 60%, transparent 40%) no-repeat 12px + 17px/4px 4px, + radial-gradient(circle, currentColor 60%, transparent 40%) no-repeat 6px + 17px/4px 4px; +} +i.icon.shopping.cart::after, +i.icon.shopping.cart::before { + left: 4px; + top: 2px; + position: absolute; + content: ""; + width: 2px; + height: 14px; + display: block; + box-sizing: border-box; + transform: skew(12deg); + background: currentColor; +} +i.icon.shopping.cart::after { + left: 16px; + top: 6px; + height: 10px; + transform: skew(-12deg); +} diff --git a/mirzaev/ebala/system/public/css/icons/smartphone.css b/mirzaev/ebala/system/public/css/icons/smartphone.css new file mode 100644 index 0000000..fc2aa8e --- /dev/null +++ b/mirzaev/ebala/system/public/css/icons/smartphone.css @@ -0,0 +1,22 @@ +i.icon.smartphone { + position: relative; + --width: 14px; + --height: 20px; + width: 14px; + height: 20px; + display: block; + box-sizing: border-box; + transform: scale(var(--ggs, 1)); + border: 2px solid; + border-radius: 2px; + background: linear-gradient(to left, currentColor 5px, transparent 0) + no-repeat 4px 12px/2px 2px; +} + +label > i.icon.nametag:first-child { + left: 5px; +} + +i.icon.nametag + input { + padding-left: 32px; +} diff --git a/mirzaev/ebala/system/public/css/icons/timer.css b/mirzaev/ebala/system/public/css/icons/timer.css new file mode 100644 index 0000000..bfe018c --- /dev/null +++ b/mirzaev/ebala/system/public/css/icons/timer.css @@ -0,0 +1,41 @@ +i.timer, +i.timer::before { + --width: 18px; + --height: 18px; + width: 18px; + height: 18px; + border-radius: 40px; + border: 2px solid; +} +i.timer { + position: relative; + display: block; + box-sizing: border-box; + border-top-color: transparent; + background: linear-gradient(to left, currentColor 10px, transparent 0) + no-repeat 6px -2px/2px 6px; +} +i.timer::after, +i.timer::before { + position: absolute; + content: ""; + display: block; + box-sizing: border-box; +} +i.timer::before { + top: -2px; + right: -2px; + transform: rotate(45deg); + border-right-color: transparent; + border-left-color: transparent; + border-bottom-color: transparent; +} +i.timer::after { + left: 4px; + bottom: 5px; + width: 2px; + height: 6px; + border-radius: 100px; + transform: rotate(-50deg); + background: currentColor; +} diff --git a/mirzaev/ebala/system/public/css/icons/user.css b/mirzaev/ebala/system/public/css/icons/user.css new file mode 100644 index 0000000..2dc2f43 --- /dev/null +++ b/mirzaev/ebala/system/public/css/icons/user.css @@ -0,0 +1,55 @@ +i.icon.user { + --width: 12px; + --height: 18px; + width: 12px; + height: 18px; + display: block; + box-sizing: border-box; +} + +i.icon.user::before, +i.icon.user::after { + position: absolute; + content: ""; + display: block; + box-sizing: border-box; + border: 2px solid; +} + +i.icon.user.bold::before, +i.icon.user.bold::after { + border: 3px solid; +} + +i.icon.user::before { + left: 2px; + top: 0; + width: 8px; + height: 8px; + border-radius: 30px; +} + +i.icon.user.bold::before { + left: 2px; + top: -2px; + width: 9px; + height: 9px; +} + +i.icon.user::after { + top: 9px; + width: 12px; + height: 9px; + border-bottom: 0; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} + +i.icon.user.bold::after { + top: 9px; + width: 13px; + height: 10px; + border-bottom: 0; + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} diff --git a/mirzaev/ebala/system/public/css/icons/user_add.css b/mirzaev/ebala/system/public/css/icons/user_add.css new file mode 100755 index 0000000..2c87e83 --- /dev/null +++ b/mirzaev/ebala/system/public/css/icons/user_add.css @@ -0,0 +1,46 @@ +i.icon.user.add { + --width: 20px; + --height: 18px; + width: 20px; + height: 18px; + display: block; + box-sizing: border-box; + background: linear-gradient(to left, currentColor 8px, transparent 0) + no-repeat 14px 6px/6px 2px, + linear-gradient(to left, currentColor 8px, transparent 0) no-repeat 16px 4px/2px + 6px; +} + +i.icon.user.add::after, +i.icon.user.add::before { + content: ""; + position: absolute; + display: block; + box-sizing: border-box; + border: 2px solid; +} + +i.icon.user.add::before { + left: 2px; + top: 0; + width: 8px; + height: 8px; + border-radius: 30px; +} + +i.icon.user.add::after { + top: 9px; + width: 12px; + height: 9px; + border-bottom: 0; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} + +label > i.icon.user.add:first-child { + left: 9px; +} + +i.icon.user.add + input { + padding-left: 37px; +} diff --git a/mirzaev/ebala/system/public/css/icons/work_alt.css b/mirzaev/ebala/system/public/css/icons/work_alt.css new file mode 100644 index 0000000..13e0098 --- /dev/null +++ b/mirzaev/ebala/system/public/css/icons/work_alt.css @@ -0,0 +1,24 @@ +i.icon.work.alt, +i.icon.work.alt::after { + display: block; + box-sizing: border-box; + box-shadow: 0 0 0 2px; +} +i.icon.work.alt { + position: relative; + --width: 14px; + --height: 10px; + width: 14px; + height: 10px; + border-radius: 1px; +} +i.icon.work.alt::after { + left: 4px; + top: -3px; + position: absolute; + content: ""; + width: 6px; + height: 1px; + border-top-left-radius: 1px; + border-top-right-radius: 1px; +} diff --git a/mirzaev/ebala/system/public/css/list.css b/mirzaev/ebala/system/public/css/list.css new file mode 100755 index 0000000..8e517dc --- /dev/null +++ b/mirzaev/ebala/system/public/css/list.css @@ -0,0 +1,201 @@ +main { + z-index: 1000; + margin-top: 15vh; + position: relative; + height: unset; + display: flex; + flex-direction: unset; + justify-content: center; + align-items: unset; +} + +section.panel.list { + min-width: 800px; +} + +section.panel.list.medium { + width: 80%; +} + +section.panel.list > form.row.menu { + margin-bottom: 10px; + transition: 0s; +} + +section.panel.list > form.row.menu > label { + height: max-content; + min-height: 30px; + display: flex; +} + +section.panel.list > form.row.menu > label:not(.solid) { + gap: 15px; +} + +section.panel.list > form.row.menu.wide > label { + height: 36px; +} + +section.panel.list > form.row.menu:is(.separated, :last-of-type) { + margin-bottom: 20px; +} + +section.panel.list > form.row.menu > label > button { + height: 30px; + align-self: center; +} + +section.panel.list > form.row.menu.stretched > label > button, +section.panel.list > form.row.menu.stretched > label > input[type="search"] { + flex-grow: 1; +} + +section.panel.list > form.row.menu > label > input { + padding: 0 10px; +} + +section.panel.list > form.row.menu > label > input:not(.merged) { + border-radius: 3px; +} + +section.panel.list > form.row.menu > label > input[type="date"] { + width: 115px; + flex-shrink: 0; +} + +section.panel.list > form.row.menu > label > input[type="search"] + button { + height: 100%; + padding: 0 30px; + flex-grow: 0; +} + +section.panel.list > div#title { + height: 50px; + background-color: var(--background-below-6); +} + +section.panel.list > div#title > span { + font-weight: unset; + font-size: unset; + color: unset; +} + +section.panel.list > div.row { + position: relative; + height: 35px; + display: flex; + gap: 12px; + padding: 0 12px; + background-color: var(--background-above-1); +} + +section.panel.list > div.row:first-of-type { + border-radius: 3px 3px 0 0; +} + +section.panel.list > div.row:last-of-type { + border-radius: 0 0 3px 3px; +} + +section.panel.list > div.row:hover * { + transition: unset; +} + +section.panel.list > div.row:nth-of-type(2n + 1) { + background-color: var(--background-above-2); +} + +section.panel.list > div.row[data-selected="true"]::before { + left: -25px; + top: 0.08rem; + position: absolute; + align-self: center; + content: ""; + display: inline list-item; + font-size: 1.5rem; + list-style-type: disclosure-closed; + list-style-position: inside; + animation-duration: 0.8s; + animation-iteration-count: infinite; + animation-direction: alternate-reverse; + animation-name: list-triangle-left; + animation-fill-mode: forwards; + animation-timing-function: ease-out; + color: var(--interface-brown); +} + +section.panel.list > div.row[data-selected="true"]::after { + right: -25px; + bottom: 0.08rem; + rotate: 180deg; + position: absolute; + align-self: center; + content: ""; + display: inline list-item; + font-size: 1.5rem; + list-style-type: disclosure-closed; + list-style-position: inside; + animation-duration: 0.8s; + animation-iteration-count: infinite; + animation-direction: alternate-reverse; + animation-name: list-triangle-right; + animation-fill-mode: forwards; + animation-timing-function: ease-out; + color: var(--interface-brown); +} + +section.panel.list > div.row.confirmed { + background-color: var(--grass-background-above-2); +} + +section.panel.list > div.row.confirmed:nth-of-type(2n + 1) { + background-color: var(--grass-background-above-1); +} + +section.panel.list > div.row.hided { + /* cursor: not-allowed !important; */ + box-shadow: 0px 0px 0px 1000px rgba(0, 0, 0, 0.2) inset; + -webkit-box-shadow: 0px 0px 0px 1000px rgba(0, 0, 0, 0.2) inset; + -moz-box-shadow: 0px 0px 0px 1000px rgba(0, 0, 0, 0.2) inset; +} + +/* +section.panel.list > div.row.hided:hover { + box-shadow: unset; + -webkit-box-shadow: unset; + -moz-box-shadow: unset; +} */ + +section.panel.list > div.row.hided * { + filter: blur(1px); + opacity: 0.3; +} + +section.panel.list > div.row.hided:hover * { + filter: unset; + opacity: unset; +} + +section.panel.list > div.row > span { + position: relative; + margin: auto 0; + padding: 8px 0; + text-align: left; +} + +section.panel.list > div.row:nth-of-type(1) > span { + text-align: center; +} + +section.panel.list > div.row:nth-of-type(1) > span > i { + position: relative; + margin: auto; +} + +section.panel.list > div.row > span[onclick] { + cursor: pointer; +} + +section.panel.list > div.row > span.field { + cursor: text; +} diff --git a/mirzaev/ebala/system/public/css/main.css b/mirzaev/ebala/system/public/css/main.css new file mode 100755 index 0000000..497197d --- /dev/null +++ b/mirzaev/ebala/system/public/css/main.css @@ -0,0 +1,739 @@ +/*@media (prefers-color-scheme: above) {*/ +:root { + --background-above-5: #a69c9c; + --background-above-4: #b9b8b8; + --background-above-3: #ecdfdf; + --background-above-2: #f0eded; + --background-above-1: #fffbfb; + --background-above: #f4eaea; + --background: #e8d9d9; + --background-below: #d7c5c5; + --background-below-6: #8e8282; + --background-inverse: #221e1e; + --background-inverse-below: #120f0f; + --node-background-important: #c3eac3; + --node-background-completed: #b0c0b0; + --node-background: #bdb; + --connection: #b2b7b2; + --connection-completed: #d1d1d1; + --text: #151313; + --text-hover: #463e3e; + --text-active: #0e0e0e; + --text-inverse-above: #fff; + --text-inverse: #efefef; + --text-inverse-below: #d0d0d0; + --text-inverse-below-1: #bbadad; + + /* --beige-text-above: #defaff; + --beige-text: #806d64; + --beige-text-below: #72c2d0; + --beige-background-above: #ffffda; + --beige-background: #f4f4e1; + --beige-background-below: #f4f4e1; */ + + --clay-text-above: #ffe9be; + --clay-text: #f7d1a1; + --clay-text-below: #c8af7d; + --clay-background-above-1: #dc4343; + --clay-background-above: #bf3737; + --clay-background: #a43333; + --clay-background-below: #8d2a2a; + --clay-background-below-1: #792727; + + --grass-text-above: #fcffae; + --grass-text: #fbff80; + --grass-text-below: #e3e85e; + --grass-background-above-2: #79b757; + --grass-background-above-1: #89c866; + --grass-background-above: #42ce1c; + --grass-background: #3dbb1a; + --grass-background-below: #38a818; + + --earth-text-above: #975151; + --earth-text: #794040; + --earth-text-below: #511f1f; + --earth-background-above: #d9c5b3; + --earth-background: #b0a296; + --earth-background-below: #918377; + + --sand-text-above: #c8b25f; + --sand-text: #b0844a; + --sand-text-below: #976b31; + --sand-text-below-1: #6a4a1f; + --sand-background-above: #fbf4d9; + --sand-background: #fff1bb; + --sand-background-below: #dfc79a; + + --river-text-above: #2b91c4; + --river-text: #335cbb; + --river-text-below: #2b4480; + --river-background-above: #bbc9ff; + --river-background: #9dadec; + --river-background-below: #7a8cd7; + + --sea-text-above: #ccf8ff; + --sea-text: #b7f1fb; + --sea-text-below: #99d5df; + --sea-background-above: #6a68dd; + --sea-background: #5d5bc1; + --sea-background-below: #504f9f; + + --sky-text-above: #43b0c1; + --sky-text: #1e4c53; + --sky-text-below: ; + --sky-background-above: #dfffff; + --sky-background: #e6f9ff; + --sky-background-below: ; + + --input-error: #d25050; + --icon-error: #792323; + --interface-brown: #9f704e; + --menu-background: #775653; + --menu-text-above: #ffd88e; + /* --menu-text: #cab59b; */ + --menu-text: #d0b88a; + --menu-text-below: #a48f68; + --input-beige: #fffce8; + --input-clay: #fff8f8; +} +/*}*/ + +::selection { + color: var(--clay-text-above); + background-color: var(--clay-background-above); +} + +:root { + --link: #3c76ff; + --link-hover: #6594ff; + --link-active: #3064dd; +} + +.unselectable { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.hidden:not(.animation) { + display: none !important; +} + +* { + text-decoration: none; + outline: none; + border: none; + color: var(--text); + font-family: Fira, sans-serif; + transition: 0.1s ease-out; +} + +pre, +code { + font-family: Hack, monospace; +} + +button, +input[type="submit"], +input[type="range"] { + cursor: pointer; +} + +input[type="range"] { + margin: unset; + background: transparent; +} + +input[type="range"]::-moz-range-track, +input[type="range"]::-webkit-slider-runnable-track { + height: 6px; + border-radius: 3px; +} + +input[type="range"].sand::-moz-range-track, +input[type="range"].sand::-webkit-slider-runnable-track { + border: 1px solid var(--sand-background-below); + background-color: var(--sand-background); +} + +input[type="range"]::-moz-range-progress { + height: 6px; +} + +input[type="range"].sand::-moz-range-progress { + background-color: var(--sand-text); +} + +input[type="range"]::-moz-range-thumb, +input[type="range"]::-webkit-slider-thumb { + width: 20px; + height: 20px; + cursor: grab; + border-radius: 100%; +} + +input[type="range"].sand::-moz-range-thumb, +input[type="range"].sand::-webkit-slider-thumb { + border: 2px solid var(--sand-text-below); + background-color: var(--sand-text-above); +} + +input[type="range"]:active::-moz-range-thumb, +input[type="range"]:active::-webkit-slider-thumb { + cursor: grabbing; +} + +input[type="range"] + datalist > option { + cursor: help; +} + +div.range { + position: relative; + display: flex; + flex-direction: column; +} + +div.range.small { + width: 140px; + flex-shrink: 0; +} + +div.range.small > input[type="range"] { + margin: auto 0; +} + +div.range.small > input[type="range"] + i.value { + position: absolute; + left: var(--left, 0px); + margin-left: -10.5px; + width: 21px; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + font-style: normal; + font-weight: bold; + pointer-events: none; + transition: 0s; +} + +div.range.small > input[type="range"].sand + i.value { + color: var(--sand-text-below-1); +} + +div.range > datalist { + display: flex; + justify-content: space-between; + padding: 0 3px; +} + +input.beige { + background-color: var(--input-beige); +} + +input.clay { + background-color: var(--input-clay); +} + +:is(input, button).merged.right { + margin-right: 0; + border-radius: 3px 0 0 3px; +} + +:is(input, button).merged.left { + margin-left: 0; + border-radius: 0 3px 3px 0; +} + +button:is(.transparent, .transparent:hover, .transparent:active) { + background: unset; +} + +a { + color: var(--link); +} + +a:hover { + color: var(--link-hover); +} + +a:active { + color: var(--link-active); + transition: unset; +} + +label { + position: relative; + height: 26px; + display: flex; + overflow: hidden; + border-radius: 2px; +} + +label > i:first-of-type { + left: 8px; + top: calc((26px - var(--height)) / 2); + position: absolute !important; + margin: auto; + color: var(--text-inverse-below-1); +} + +label * { + /* color: var(--text-inverse); */ +} + +*[disabled], +*[readonly="true"] { + cursor: not-allowed; + /* filter: contrast(2) grayscale(0.8); */ + filter: contrast(0.6) grayscale(0.7); +} + +select { + padding: 3px 13px; + cursor: pointer; + border-radius: 3px; + background-color: var(--background-below); +} + +textarea { + width: 396px; + min-width: calc(100% - 28px); + min-height: 120px; + max-width: calc(100% - 28px); + max-height: 300px; + padding: 8px 14px; + font-size: smaller; + overflow: hidden; + border-radius: 3px; + transition: 0s; +} + +input { + border-radius: 3px; +} + +input.small { + width: 50px; +} + +input.medium { + width: 140px; +} + +input.center { + text-align: center; +} + +label > input { + height: initial; + padding: 0 8px; + cursor: text; + background-color: var(--background-above-1); +} + +label > *:first-child.separated.right { + margin: auto auto auto 0; +} + +i.icon { + z-index: 10; +} + +i.icon + input { + padding-left: 30px; +} + +i.icon.error { + animation-duration: 3s; + animation-name: icon-error; + animation-fill-mode: forwards; + animation-timing-function: ease-out; +} + +:is(input, textarea).error { + animation-duration: 1s; + animation-name: input-error; + animation-fill-mode: forwards; + animation-timing-function: ease-in; +} + +section.header > h1 { + font-size: 1.3rem; + line-height: 1.3rem; +} + +section.header > :is(h2, h3) { + font-size: 1.1rem; + line-height: 1.1rem; +} + +body { + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + background-color: var(--background); +} + +body > div.background { + z-index: -50000; + left: -350%; + top: -350%; + width: 500%; + height: 500%; + position: absolute; + filter: blur(200px); + animation-duration: 15s; + animation-name: page-background-gradient; + animation-iteration-count: infinite; + background-repeat: no-repeat; + animation-timing-function: linear; + background-image: radial-gradient( + circle, + var(--background-above) 0%, + rgba(0, 0, 0, 0) 100% + ); +} + +aside { + z-index: 500; + grid-column: 1/ 4; + grid-row: 2; + overflow: hidden; +} + +header { + z-index: 5000; + position: absolute; + display: flex; + flex-direction: column; + box-shadow: 2px 0 5px rgba(0, 0, 0, 0.3); +} + +main { + height: unset; + display: flex; + flex-direction: column; + justify-content: unset; + align-items: center; + gap: 10px; + transition: 0s; +} + +footer { + z-index: 3000; + position: absolute; +} + +.stretched { + width: 100%; + flex-grow: 10; +} + +button { + height: auto; + padding: 0 20px; + font-weight: bold; + border-radius: 3px; +} + +button.grass { + color: var(--grass-text); + background-color: var(--grass-background); +} + +button.grass * { + color: var(--grass-text); +} + +button.grass:hover { + color: var(--grass-text-above); + background-color: var(--grass-background-above); +} + +button.grass:hover * { + color: var(--grass-text-above); +} + +button.grass:active { + color: var(--grass-text-below); + background-color: var(--grass-background-below); + transition: unset; +} + +button.grass:active * { + color: var(--grass-text-below); + transition: unset; +} + +button.blue { + color: var(--blue-text); + background-color: var(--blue-background); +} + +button.blue * { + color: var(--blue-text); +} + +button.blue:hover { + color: var(--blue-text-above); + background-color: var(--blue-background-above); +} + +button.blue:hover * { + color: var(--blue-text-above); +} + +button.blue:active { + color: var(--blue-text-below); + background-color: var(--blue-background-below); + transition: unset; +} + +button.blue:active * { + color: var(--blue-text-below); + transition: unset; +} + +.clay { + color: var(--clay-text); + background-color: var(--clay-background); +} + +.clay * { + color: var(--clay-text); +} + +button.clay:hover { + color: var(--clay-text-above); + background-color: var(--clay-background-above); +} + +button.clay:hover * { + color: var(--clay-text-above); +} + +button.clay:active { + color: var(--clay-text-below); + background-color: var(--clay-background-below); + transition: unset; +} + +button.clay:active * { + color: var(--clay-text-below); + transition: unset; +} + +.earth { + color: var(--earth-text); + background-color: var(--earth-background); +} + +.earth * { + color: var(--earth-text); +} + +button.earth:hover { + color: var(--earth-text-above); + background-color: var(--earth-background-above); +} + +button.earth:hover * { + color: var(--earth-text-above); +} + +button.earth:active { + color: var(--earth-text-below); + background-color: var(--earth-background-below); + transition: unset; +} + +button.earth:active * { + color: var(--earth-text-below); + transition: unset; +} + +.sand { + color: var(--sand-text); + background-color: var(--sand-background); +} + +.sand * { + color: var(--sand-text); +} + +button.sand:hover { + color: var(--sand-text-above); + background-color: var(--sand-background-above); +} + +button.sand:hover * { + color: var(--sand-text-above); +} + +button.sand:active { + color: var(--sand-text-below); + background-color: var(--sand-background-below); + transition: unset; +} + +button.sand:active * { + color: var(--sand-text-below); + transition: unset; +} + +.river { + color: var(--river-text); + background-color: var(--river-background); +} + +.river * { + color: var(--river-text); +} + +button.river:hover { + color: var(--river-text-above); + background-color: var(--river-background-above); +} + +button.river:hover * { + color: var(--river-text-above); +} + +button.river:active { + color: var(--river-text-below); + background-color: var(--river-background-below); + transition: unset; +} + +button.river:active * { + color: var(--river-text-below); + transition: unset; +} + +.sky { + color: var(--sky-text); + background-color: var(--sky-background); +} + +.sky * { + color: var(--sky-text); +} + +button.sky:hover { + color: var(--sky-text-above); + background-color: var(--sky-background-above); +} + +button.sky:hover * { + color: var(--sky-text-above); +} + +button.sky:active { + color: var(--sky-text-below); + background-color: var(--sky-background-below); + transition: unset; +} + +button.sky:active * { + color: var(--sky-text-below); + transition: unset; +} + +.sea { + color: var(--sea-text); + background-color: var(--sea-background); +} + +.sea * { + color: var(--sea-text); +} + +button.sea:hover { + color: var(--sea-text-above); + background-color: var(--sea-background-above); +} + +button.sea:hover * { + color: var(--sea-text-above); +} + +button.sea:active { + color: var(--sea-text-below); + background-color: var(--sea-background-below); + transition: unset; +} + +button.sea:active * { + color: var(--sea-text-below); + transition: unset; +} + +button.wide { + flex-grow: 1; +} + +section#menu { + position: absolute; + width: 100%; + height: 120px; +} + +section#menu > nav { + top: 0; + position: sticky; + display: flex; + justify-content: center; + background: var(--menu-background); +} + +section#menu > nav > ul { + margin: 0; + min-width: 800px; + width: 80%; + height: 45px; + padding: 0; + display: flex; + gap: 10px; + list-style: none; +} + +section#menu > nav > ul > li { + height: 100%; + display: flex; + align-items: center; +} + +section#menu > nav > ul > li.divided { + margin-left: 9px; +} + +section#menu > nav > ul > li.divided:before { + content: ""; + margin-right: 9px; + height: 60%; + border-left: 1px solid var(--menu-text-below); +} + +section#menu > nav > ul > li#account:last-child { + margin-left: auto; +} + +section#menu > nav > ul > li > button { + height: 100%; + padding: 0 30px; + color: var(--menu-text); +} + +section#menu > nav > ul > li > button:hover { + color: var(--menu-text-above); +} + +section#menu > nav > ul > li > button:active { + transition: 0s; + color: var(--menu-text-below); +} diff --git a/mirzaev/ebala/system/public/css/pages/tasks.css b/mirzaev/ebala/system/public/css/pages/tasks.css new file mode 100755 index 0000000..c66bf54 --- /dev/null +++ b/mirzaev/ebala/system/public/css/pages/tasks.css @@ -0,0 +1,108 @@ +section#tasks.panel.list > div.row:nth-of-type(1) > span[data-column="end"] > i.home { + margin-top: 5px; + height: 12px; +} + +section#tasks.panel.list + > div.row + > span:is( + [data-column="worker"], + [data-column="name"], + [data-column="task"], + [data-column="address"], + [data-column="type"], + [data-column="tax"], + [data-column="commentary"], + [data-column="chat"] + ) { + min-width: 220px; + width: 220px; + display: ruby; + text-overflow: ellipsis; + overflow: hidden; +} + +section#tasks.panel.list > div.row > span[data-column="date"] { + min-width: 101px; + width: 101px; + font-weight: bold; +} + +section#tasks.panel.list > div.row > span[data-column="worker"] { + min-width: 67px; + width: 67px; + font-weight: bold; +} + +section#tasks.panel.list > div.row:nth-of-type(1) > span[data-column="worker"] { + margin-top: 8px; +} + +section#tasks.panel.list > div.row > span[data-column="name"] { + min-width: 200px; + width: 200px; +} + +section#tasks.panel.list > div.row:nth-of-type(1) > span[data-column="start"] { + margin-top: 13px; +} + +section#tasks.panel.list > div.row > span[data-column="start"] { + min-width: 37px; + width: 37px; + font-size: small; +} + +section#tasks.panel.list > div.row > span[data-column="end"] { + min-width: 37px; + width: 37px; + font-size: small; +} + +section#tasks.panel.list > div.row:nth-of-type(1) > span[data-column="hours"] { + margin-bottom: 8px; +} + +section#tasks.panel.list > div.row > span[data-column="hours"] { + min-width: 27px; + width: 27px; + font-size: small; +} + +section#tasks.panel.list > div.row:nth-of-type(1) > span[data-column="market"] { + margin-bottom: 8px; +} + +section#tasks.panel.list > div.row > span[data-column="market"] { + min-width: 46px; + width: 46px; + font-weight: bold; +} + +section#tasks.panel.list > div.row > span[data-column="address"] { + min-width: 180px; + width: 180px; +} + +section#tasks.panel.list > div.row > span[data-column="type"] { + min-width: 53px; + width: 53px; + font-size: small; +} + +section#tasks.panel.list > div.row > span[data-column="tax"] { + min-width: 55px; + width: 55px; + font-size: small; +} + +section#tasks.panel.list > div.row > span[data-column="commentary"] { + min-width: unset; + width: 100%; +} + +section#tasks.panel.list > div.row > span[data-column="chat"] { + min-width: 65px; + width: 65px; + text-align: center; +} diff --git a/mirzaev/ebala/system/public/css/pages/workers.css b/mirzaev/ebala/system/public/css/pages/workers.css new file mode 100755 index 0000000..29e3cd0 --- /dev/null +++ b/mirzaev/ebala/system/public/css/pages/workers.css @@ -0,0 +1,93 @@ +section#workers.panel.list + > div.row:nth-of-type(1) + > span[data-column="end"] + > i.home { + margin-top: 5px; + height: 12px; +} + +section#workers.panel.list + > div.row + > span:is( + [data-column="id"], + [data-column="name"], + [data-column="birth"], + [data-column="number"], + [data-column="passport"], + [data-column="department"], + [data-column="city"], + [data-column="address"], + [data-column="requisites"], + [data-column="tax"], + [data-column="commentary"], + [data-column="status"], + ) { + min-width: 220px; + width: 220px; + display: ruby; + text-overflow: ellipsis; + overflow: hidden; +} + +section#workers.panel.list > div.row > span[data-column="id"] { + min-width: 67px; + width: 67px; + font-weight: bold; +} + +section#workers.panel.list > div.row:nth-of-type(1) > span[data-column="id"] { + margin-top: 8px; +} + +section#workers.panel.list > div.row > span[data-column="name"] { + min-width: 200px; + width: 200px; +} + +section#workers.panel.list > div.row > span[data-column="birth"] { + min-width: 80px; + width: 80px; + font-size: small; +} + +section#workers.panel.list > div.row > span[data-column="number"] { + min-width: 120px; + width: 120px; +} + +section#workers.panel.list > div.row > span[data-column="passport"] { + min-width: 150px; + width: 150px; +} + +section#workers.panel.list > div.row > span[data-column="city"] { + min-width: 90px; + width: 90px; +} + +section#workers.panel.list > div.row > span[data-column="address"] { + min-width: 180px; + width: 180px; +} + +section#workers.panel.list > div.row > span[data-column="requisites"] { + min-width: 180px; + width: 180px; +} + +section#workers.panel.list > div.row > span[data-column="tax"] { + min-width: 55px; + width: 55px; + font-size: small; +} + +section#workers.panel.list > div.row > span[data-column="commentary"] { + min-width: unset; + width: 100%; +} + +section#workers.panel.list > div.row > span[data-column="status"] { + min-width: 100px; + width: 100px; + font-size: small; +} diff --git a/mirzaev/ebala/system/public/css/popup.css b/mirzaev/ebala/system/public/css/popup.css new file mode 100644 index 0000000..f335a8d --- /dev/null +++ b/mirzaev/ebala/system/public/css/popup.css @@ -0,0 +1,137 @@ +div#popup { + z-index: 999999999999; + left: 0; + top: 0; + position: fixed; + width: 100%; + height: 100%; + display: flex; + gap: 12px; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: rgba(18, 3, 3, 0.4); +} + +div#popup > section { + background-color: var(--background-above-2); +} + +div#popup > section.errors { + top: var(--top, 80%); + position: absolute; + height: var(--height); + padding: 8px 30px !important; +} + +div#popup > section.small { + width: 420px; +} + +div#popup > section.list { + display: flex; + flex-direction: column; + gap: 8px; + padding: 30px; + border-radius: 3px; +} + +div#popup > section.list > h3 { + margin-top: 4px; + margin-bottom: 18px; + width: 100%; + text-align: center; +} + +div#popup > section.list > span { + width: 100%; + display: flex; + text-align: right; +} + +div#popup > section.list > span > b { + margin-right: auto; + text-align: left; +} + +div#popup > section.list > :is(div, select).row.buttons { + height: 33px; +} + +div#popup > section.list > :is(div, select).row:not(.buttons, .stretchable), +div#popup > section.list > :is(div, select).row:not(.buttons, .stretchable) > button { + height: 29px; +} + +div#popup > section.list > div.row:not(.merged) + div.row.merged { + margin-top: 8px; +} + +div#popup > section.list > div.row:not(.merged) { + margin-top: 8px; +} + +div#popup > section.list > div.row:not(.merged):last-of-type { + margin-top: 8px; +} + +div#popup > section.list > div.row.divided { + margin-top: 8px; +} + +div#popup > section.list > :not(h3):first-child:not(.divided), +div#popup > section.list > h3 + div.row:not(.divided) { + margin-top: unset !important; +} + +div#popup > section.list > :only-child { + margin: unset !important; +} + +div#popup > section.list > div.row { + height: fit-content; + position: relative; + display: flex; + justify-content: center; + transition: 0s; +} + +div#popup > section.list > div.row:not(.monolithic) { + gap: 10px; +} + +div#popup > section.list > div.row > label { + display: contents; +} + +div#popup > section.list > div.row > label > i { + left: 13px; + top: 7px; + position: absolute; +} + +div#popup > section.list > div.row > label > input[type="date"]:not(.small, .medium) { + flex-grow: 1; + text-align: center; +} + +div#popup > section.list > div.row > label > input[type="time"]:not(.small, .medium) { + width: 55px; + text-align: center; +} + +div#popup > section.list > div.row > label > :is(input, button):only-child { + width: 100%; +} + +div#popup > section.list > div.row > label > :is(input, select):only-of-type:not(.small, .medium) { + width: 100%; +} + +/* div#popup > section.list > div.row > label > input:only-of-type:not(.center) { + padding-left: 37px; +} */ + +div#popup > section.list.errors > section.body > dl > dd { + margin-left: 20px; +} diff --git a/mirzaev/ebala/system/public/index.php b/mirzaev/ebala/system/public/index.php new file mode 100755 index 0000000..3c26d8a --- /dev/null +++ b/mirzaev/ebala/system/public/index.php @@ -0,0 +1,85 @@ +write('/', 'index', 'index', 'GET'); +$router->write('/', 'index', 'index', 'POST'); +$router->write('/workers', 'worker', 'index', 'GET'); +$router->write('/workers', 'worker', 'index', 'POST'); +$router->write('/markets', 'market', 'index', 'GET'); +$router->write('/markets', 'market', 'index', 'POST'); +$router->write('/operators', 'operator', 'index', 'GET'); +$router->write('/operators', 'operator', 'index', 'POST'); +$router->write('/administrators', 'administrators', 'index', 'GET'); +$router->write('/administrators', 'administrators', 'index', 'POST'); +$router->write('/settings', 'settings', 'index', 'GET'); +$router->write('/settings', 'settings', 'index', 'POST'); +$router->write('/$id', 'account', 'index', 'GET'); +$router->write('/$id', 'account', 'index', 'POST'); +$router->write('/session/worker', 'session', 'worker', 'POST'); +$router->write('/session/write', 'session', 'write', 'POST'); +$router->write('/session/read', 'session', 'read', 'POST'); +$router->write('/session/administrator', 'session', 'administrator', 'POST'); +$router->write('/session/operator', 'session', 'operator', 'POST'); +$router->write('/session/market', 'session', 'market', 'POST'); +$router->write('/session/password', 'session', 'password', 'POST'); +$router->write('/session/invite', 'session', 'invite', 'POST'); +$router->write('/tasks/create', 'task', 'create', 'POST'); +$router->write('/tasks/read', 'task', 'read', 'POST'); +$router->write('/works/list', 'work', 'datalist', 'POST'); +$router->write('/task/$task/read', 'task', 'task', 'POST'); +$router->write('/task/$task/value', 'task', 'value', 'POST'); +$router->write('/task/$task/confirm', 'task', 'confirm', 'POST'); +$router->write('/task/$task/hide', 'task', 'hide', 'POST'); +$router->write('/task/$task/remove', 'task', 'remove', 'POST'); +$router->write('/task/$task/work', 'task', 'work', 'POST'); +$router->write('/task/$task/date', 'task', 'date', 'POST'); +$router->write('/task/$task/works', 'task', 'works', 'POST'); +$router->write('/task/$task/description', 'task', 'description', 'POST'); +$router->write('/task/$task/commentary', 'task', 'commentary', 'POST'); +$router->write('/task/$task/worker/update', 'task', 'update', 'POST'); +$router->write('/task/$task/market/update', 'task', 'update', 'POST'); +$router->write('/worker/$worker/read', 'task', 'worker', 'POST'); +$router->write('/workers/read', 'worker', 'read', 'POST'); +$router->write('/workers/list', 'worker', 'datalist', 'POST'); +$router->write('/market/$market/read', 'task', 'market', 'POST'); +$router->write('/markets/list', 'market', 'datalist', 'POST'); +$router->write('/elements/menu', 'index', 'menu', 'POST'); + +// Инициализация ядра +$core = new core(namespace: __NAMESPACE__, router: $router, controller: new controller(false), model: new model(false)); + + +// Обработка запроса +echo $core->start(); diff --git a/mirzaev/ebala/system/public/js/buffer.js b/mirzaev/ebala/system/public/js/buffer.js new file mode 100644 index 0000000..67abee3 --- /dev/null +++ b/mirzaev/ebala/system/public/js/buffer.js @@ -0,0 +1,84 @@ +"use strict"; + +if (typeof window.buffer !== "function") { + // Not initialized + + // Initialize of the class in global namespace + window.buffer = class buffer { + /** + * Записать в буфер + * + * @param {string} name Название + * @param {string|number} value Значение + * + * @return {Promise} + */ + static write(name, value) { + if ( + typeof core === "function" && typeof name === "string" && + (typeof value === "string" || typeof value === "number") + ) { + // Инициализировано ядро и переданы название со значением + + // Инициализация даты удаления cookie + const expires = new Date(); + + // Расчёт даты удаления (через 1 час после создания) + expires.setTime(expires.getTime() + 3600000); + + // Запись в cookie + Cookies.set(name, value, { + expires, + path: "/", + secure: true, + httponly: false, + samesite: "strict", + }); + + // Запрос к серверу для записи в сессию (базу данных) + return fetch("/session/write", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: `name=${name}&value=${value}`, + }); + } + } + + /** + * Прочитать из буфера + * + * @param {string} name Название + * + * @return {mixed} + */ + static async read(name) { + if (typeof core === "function" && typeof name === "string") { + // Инициализировано ядро и переданы название со значением + + // Запрос к серверу для чтения из сессии (базы данных) + return await fetch("/session/read", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: `name=${name}`, + }) + .then((response) => response.text()) + .then((data) => { + return data.length > 0 + ? data + : Cookies.get(`buffer_${core.interface}_${name}`); + }); + } + } + }; +} + +// Вызов события: "инициализировано" +document.dispatchEvent( + new CustomEvent("buffer.initialized", { + detail: { buffer: window.buffer }, + }), +); diff --git a/mirzaev/ebala/system/public/js/cookies.min.js b/mirzaev/ebala/system/public/js/cookies.min.js new file mode 100755 index 0000000..d40e075 --- /dev/null +++ b/mirzaev/ebala/system/public/js/cookies.min.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self,function(){var n=e.Cookies,o=e.Cookies=t();o.noConflict=function(){return e.Cookies=n,o}}())}(this,(function(){"use strict";function e(e){for(var t=1;t { + // Деинициализация таймера + clearTimeout(timer); + + // Вызов функции (вход в рекурсию) + timer = setTimeout(() => { + func.apply(this, args); + }, timeout); + }; +} + +// Вызов события "Инициализирован демпфер" +document.dispatchEvent( + new CustomEvent("damper.initialized", { + detail: { damper }, + }), +); diff --git a/mirzaev/ebala/system/public/js/imask-7.1.0-alpha.js b/mirzaev/ebala/system/public/js/imask-7.1.0-alpha.js new file mode 100644 index 0000000..6e48107 --- /dev/null +++ b/mirzaev/ebala/system/public/js/imask-7.1.0-alpha.js @@ -0,0 +1,3263 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.IMask = {})); +})(this, (function (exports) { 'use strict'; + + /** Checks if value is string */ + function isString(str) { + return typeof str === 'string' || str instanceof String; + } + + /** Checks if value is object */ + function isObject(obj) { + var _obj$constructor; + return typeof obj === 'object' && obj != null && (obj == null ? void 0 : (_obj$constructor = obj.constructor) == null ? void 0 : _obj$constructor.name) === 'Object'; + } + function pick(obj, keys) { + if (Array.isArray(keys)) return pick(obj, (_, k) => keys.includes(k)); + return Object.entries(obj).reduce((acc, _ref) => { + let [k, v] = _ref; + if (keys(v, k)) acc[k] = v; + return acc; + }, {}); + } + + /** Direction */ + const DIRECTION = { + NONE: 'NONE', + LEFT: 'LEFT', + FORCE_LEFT: 'FORCE_LEFT', + RIGHT: 'RIGHT', + FORCE_RIGHT: 'FORCE_RIGHT' + }; + + /** Direction */ + + function forceDirection(direction) { + switch (direction) { + case DIRECTION.LEFT: + return DIRECTION.FORCE_LEFT; + case DIRECTION.RIGHT: + return DIRECTION.FORCE_RIGHT; + default: + return direction; + } + } + + /** Escapes regular expression control chars */ + function escapeRegExp(str) { + return str.replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'); + } + + // cloned from https://github.com/epoberezkin/fast-deep-equal with small changes + function objectIncludes(b, a) { + if (a === b) return true; + const arrA = Array.isArray(a), + arrB = Array.isArray(b); + let i; + if (arrA && arrB) { + if (a.length != b.length) return false; + for (i = 0; i < a.length; i++) if (!objectIncludes(a[i], b[i])) return false; + return true; + } + if (arrA != arrB) return false; + if (a && b && typeof a === 'object' && typeof b === 'object') { + const dateA = a instanceof Date, + dateB = b instanceof Date; + if (dateA && dateB) return a.getTime() == b.getTime(); + if (dateA != dateB) return false; + const regexpA = a instanceof RegExp, + regexpB = b instanceof RegExp; + if (regexpA && regexpB) return a.toString() == b.toString(); + if (regexpA != regexpB) return false; + const keys = Object.keys(a); + // if (keys.length !== Object.keys(b).length) return false; + + for (i = 0; i < keys.length; i++) if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false; + for (i = 0; i < keys.length; i++) if (!objectIncludes(b[keys[i]], a[keys[i]])) return false; + return true; + } else if (a && b && typeof a === 'function' && typeof b === 'function') { + return a.toString() === b.toString(); + } + return false; + } + + /** Selection range */ + + /** Provides details of changing input */ + class ActionDetails { + /** Current input value */ + + /** Current cursor position */ + + /** Old input value */ + + /** Old selection */ + + constructor(opts) { + Object.assign(this, opts); + + // double check if left part was changed (autofilling, other non-standard input triggers) + while (this.value.slice(0, this.startChangePos) !== this.oldValue.slice(0, this.startChangePos)) { + --this.oldSelection.start; + } + } + + /** Start changing position */ + get startChangePos() { + return Math.min(this.cursorPos, this.oldSelection.start); + } + + /** Inserted symbols count */ + get insertedCount() { + return this.cursorPos - this.startChangePos; + } + + /** Inserted symbols */ + get inserted() { + return this.value.substr(this.startChangePos, this.insertedCount); + } + + /** Removed symbols count */ + get removedCount() { + // Math.max for opposite operation + return Math.max(this.oldSelection.end - this.startChangePos || + // for Delete + this.oldValue.length - this.value.length, 0); + } + + /** Removed symbols */ + get removed() { + return this.oldValue.substr(this.startChangePos, this.removedCount); + } + + /** Unchanged head symbols */ + get head() { + return this.value.substring(0, this.startChangePos); + } + + /** Unchanged tail symbols */ + get tail() { + return this.value.substring(this.startChangePos + this.insertedCount); + } + + /** Remove direction */ + get removeDirection() { + if (!this.removedCount || this.insertedCount) return DIRECTION.NONE; + + // align right if delete at right + return (this.oldSelection.end === this.cursorPos || this.oldSelection.start === this.cursorPos) && + // if not range removed (event with backspace) + this.oldSelection.end === this.oldSelection.start ? DIRECTION.RIGHT : DIRECTION.LEFT; + } + } + + /** Applies mask on element */ + function IMask(el, opts) { + // currently available only for input-like elements + return new IMask.InputMask(el, opts); + } + + // TODO can't use overloads here because of https://github.com/microsoft/TypeScript/issues/50754 + // export function maskedClass(mask: string): typeof MaskedPattern; + // export function maskedClass(mask: DateConstructor): typeof MaskedDate; + // export function maskedClass(mask: NumberConstructor): typeof MaskedNumber; + // export function maskedClass(mask: Array | ArrayConstructor): typeof MaskedDynamic; + // export function maskedClass(mask: MaskedDate): typeof MaskedDate; + // export function maskedClass(mask: MaskedNumber): typeof MaskedNumber; + // export function maskedClass(mask: MaskedEnum): typeof MaskedEnum; + // export function maskedClass(mask: MaskedRange): typeof MaskedRange; + // export function maskedClass(mask: MaskedRegExp): typeof MaskedRegExp; + // export function maskedClass(mask: MaskedFunction): typeof MaskedFunction; + // export function maskedClass(mask: MaskedPattern): typeof MaskedPattern; + // export function maskedClass(mask: MaskedDynamic): typeof MaskedDynamic; + // export function maskedClass(mask: Masked): typeof Masked; + // export function maskedClass(mask: typeof Masked): typeof Masked; + // export function maskedClass(mask: typeof MaskedDate): typeof MaskedDate; + // export function maskedClass(mask: typeof MaskedNumber): typeof MaskedNumber; + // export function maskedClass(mask: typeof MaskedEnum): typeof MaskedEnum; + // export function maskedClass(mask: typeof MaskedRange): typeof MaskedRange; + // export function maskedClass(mask: typeof MaskedRegExp): typeof MaskedRegExp; + // export function maskedClass(mask: typeof MaskedFunction): typeof MaskedFunction; + // export function maskedClass(mask: typeof MaskedPattern): typeof MaskedPattern; + // export function maskedClass(mask: typeof MaskedDynamic): typeof MaskedDynamic; + // export function maskedClass (mask: Mask): Mask; + // export function maskedClass(mask: RegExp): typeof MaskedRegExp; + // export function maskedClass(mask: (value: string, ...args: any[]) => boolean): typeof MaskedFunction; + /** Get Masked class by mask type */ + function maskedClass(mask) /* TODO */{ + if (mask == null) throw new Error('mask property should be defined'); + if (mask instanceof RegExp) return IMask.MaskedRegExp; + if (isString(mask)) return IMask.MaskedPattern; + if (mask === Date) return IMask.MaskedDate; + if (mask === Number) return IMask.MaskedNumber; + if (Array.isArray(mask) || mask === Array) return IMask.MaskedDynamic; + if (IMask.Masked && mask.prototype instanceof IMask.Masked) return mask; + if (IMask.Masked && mask instanceof IMask.Masked) return mask.constructor; + if (mask instanceof Function) return IMask.MaskedFunction; + console.warn('Mask not found for mask', mask); // eslint-disable-line no-console + return IMask.Masked; + } + function normalizeOpts(opts) { + if (!opts) throw new Error('Options in not defined'); + if (IMask.Masked) { + if (opts.prototype instanceof IMask.Masked) return { + mask: opts + }; + + /* + handle cases like: + 1) opts = Masked + 2) opts = { mask: Masked, ...instanceOpts } + */ + const { + mask = undefined, + ...instanceOpts + } = opts instanceof IMask.Masked ? { + mask: opts + } : isObject(opts) && opts.mask instanceof IMask.Masked ? opts : {}; + if (mask) { + const _mask = mask.mask; + return { + ...pick(mask, (_, k) => !k.startsWith('_')), + mask: mask.constructor, + _mask, + ...instanceOpts + }; + } + } + if (!isObject(opts)) return { + mask: opts + }; + return { + ...opts + }; + } + + // TODO can't use overloads here because of https://github.com/microsoft/TypeScript/issues/50754 + + // From masked + // export default function createMask (opts: Opts): ReturnMasked; + // // From masked class + // export default function createMask, ReturnMasked extends Masked=InstanceType> (opts: Opts): ReturnMasked; + // export default function createMask, ReturnMasked extends MaskedDate=MaskedDate> (opts: Opts): ReturnMasked; + // export default function createMask, ReturnMasked extends MaskedNumber=MaskedNumber> (opts: Opts): ReturnMasked; + // export default function createMask, ReturnMasked extends MaskedEnum=MaskedEnum> (opts: Opts): ReturnMasked; + // export default function createMask, ReturnMasked extends MaskedRange=MaskedRange> (opts: Opts): ReturnMasked; + // export default function createMask, ReturnMasked extends MaskedRegExp=MaskedRegExp> (opts: Opts): ReturnMasked; + // export default function createMask, ReturnMasked extends MaskedFunction=MaskedFunction> (opts: Opts): ReturnMasked; + // export default function createMask, ReturnMasked extends MaskedPattern=MaskedPattern> (opts: Opts): ReturnMasked; + // export default function createMask, ReturnMasked extends MaskedDynamic=MaskedDynamic> (opts: Opts): ReturnMasked; + // // From mask opts + // export default function createMask, ReturnMasked=Opts extends MaskedOptions ? M : never> (opts: Opts): ReturnMasked; + // export default function createMask> (opts: Opts): ReturnMasked; + // export default function createMask> (opts: Opts): ReturnMasked; + // export default function createMask> (opts: Opts): ReturnMasked; + // export default function createMask> (opts: Opts): ReturnMasked; + // export default function createMask> (opts: Opts): ReturnMasked; + // export default function createMask> (opts: Opts): ReturnMasked; + // export default function createMask, ReturnMasked extends MaskedRegExp=MaskedRegExp> (opts: Opts): ReturnMasked; + // export default function createMask, ReturnMasked extends MaskedFunction=MaskedFunction> (opts: Opts): ReturnMasked; + + /** Creates new {@link Masked} depending on mask type */ + function createMask(opts) { + if (IMask.Masked && opts instanceof IMask.Masked) return opts; + const nOpts = normalizeOpts(opts); + const MaskedClass = maskedClass(nOpts.mask); + if (!MaskedClass) throw new Error('Masked class is not found for provided mask, appropriate module needs to be imported manually before creating mask.'); + if (nOpts.mask === MaskedClass) delete nOpts.mask; + if (nOpts._mask) { + nOpts.mask = nOpts._mask; + delete nOpts._mask; + } + return new MaskedClass(nOpts); + } + IMask.createMask = createMask; + + /** Generic element API to use with mask */ + class MaskElement { + /** */ + + /** */ + + /** */ + + /** Safely returns selection start */ + get selectionStart() { + let start; + try { + start = this._unsafeSelectionStart; + } catch {} + return start != null ? start : this.value.length; + } + + /** Safely returns selection end */ + get selectionEnd() { + let end; + try { + end = this._unsafeSelectionEnd; + } catch {} + return end != null ? end : this.value.length; + } + + /** Safely sets element selection */ + select(start, end) { + if (start == null || end == null || start === this.selectionStart && end === this.selectionEnd) return; + try { + this._unsafeSelect(start, end); + } catch {} + } + + /** */ + get isActive() { + return false; + } + /** */ + + /** */ + + /** */ + } + + IMask.MaskElement = MaskElement; + + /** Bridge between HTMLElement and {@link Masked} */ + class HTMLMaskElement extends MaskElement { + /** HTMLElement to use mask on */ + + constructor(input) { + super(); + this.input = input; + this._handlers = {}; + } + get rootElement() { + var _this$input$getRootNo, _this$input$getRootNo2, _this$input; + return (_this$input$getRootNo = (_this$input$getRootNo2 = (_this$input = this.input).getRootNode) == null ? void 0 : _this$input$getRootNo2.call(_this$input)) != null ? _this$input$getRootNo : document; + } + + /** + Is element in focus + */ + get isActive() { + return this.input === this.rootElement.activeElement; + } + + /** + Binds HTMLElement events to mask internal events + */ + bindEvents(handlers) { + Object.keys(handlers).forEach(event => this._toggleEventHandler(HTMLMaskElement.EVENTS_MAP[event], handlers[event])); + } + + /** + Unbinds HTMLElement events to mask internal events + */ + unbindEvents() { + Object.keys(this._handlers).forEach(event => this._toggleEventHandler(event)); + } + _toggleEventHandler(event, handler) { + if (this._handlers[event]) { + this.input.removeEventListener(event, this._handlers[event]); + delete this._handlers[event]; + } + if (handler) { + this.input.addEventListener(event, handler); + this._handlers[event] = handler; + } + } + } + /** Mapping between HTMLElement events and mask internal events */ + HTMLMaskElement.EVENTS_MAP = { + selectionChange: 'keydown', + input: 'input', + drop: 'drop', + click: 'click', + focus: 'focus', + commit: 'blur' + }; + IMask.HTMLMaskElement = HTMLMaskElement; + + /** Bridge between InputElement and {@link Masked} */ + class HTMLInputMaskElement extends HTMLMaskElement { + /** InputElement to use mask on */ + + constructor(input) { + super(input); + this.input = input; + this._handlers = {}; + } + + /** Returns InputElement selection start */ + get _unsafeSelectionStart() { + return this.input.selectionStart != null ? this.input.selectionStart : this.value.length; + } + + /** Returns InputElement selection end */ + get _unsafeSelectionEnd() { + return this.input.selectionEnd; + } + + /** Sets InputElement selection */ + _unsafeSelect(start, end) { + this.input.setSelectionRange(start, end); + } + get value() { + return this.input.value; + } + set value(value) { + this.input.value = value; + } + } + IMask.HTMLMaskElement = HTMLMaskElement; + + class HTMLContenteditableMaskElement extends HTMLMaskElement { + /** Returns HTMLElement selection start */ + get _unsafeSelectionStart() { + const root = this.rootElement; + const selection = root.getSelection && root.getSelection(); + const anchorOffset = selection && selection.anchorOffset; + const focusOffset = selection && selection.focusOffset; + if (focusOffset == null || anchorOffset == null || anchorOffset < focusOffset) { + return anchorOffset; + } + return focusOffset; + } + + /** Returns HTMLElement selection end */ + get _unsafeSelectionEnd() { + const root = this.rootElement; + const selection = root.getSelection && root.getSelection(); + const anchorOffset = selection && selection.anchorOffset; + const focusOffset = selection && selection.focusOffset; + if (focusOffset == null || anchorOffset == null || anchorOffset > focusOffset) { + return anchorOffset; + } + return focusOffset; + } + + /** Sets HTMLElement selection */ + _unsafeSelect(start, end) { + if (!this.rootElement.createRange) return; + const range = this.rootElement.createRange(); + range.setStart(this.input.firstChild || this.input, start); + range.setEnd(this.input.lastChild || this.input, end); + const root = this.rootElement; + const selection = root.getSelection && root.getSelection(); + if (selection) { + selection.removeAllRanges(); + selection.addRange(range); + } + } + + /** HTMLElement value */ + get value() { + return this.input.textContent || ''; + } + set value(value) { + this.input.textContent = value; + } + } + IMask.HTMLContenteditableMaskElement = HTMLContenteditableMaskElement; + + /** Listens to element events and controls changes between element and {@link Masked} */ + class InputMask { + /** + View element + */ + + /** Internal {@link Masked} model */ + + constructor(el, opts) { + this.el = el instanceof MaskElement ? el : el.isContentEditable && el.tagName !== 'INPUT' && el.tagName !== 'TEXTAREA' ? new HTMLContenteditableMaskElement(el) : new HTMLInputMaskElement(el); + this.masked = createMask(opts); + this._listeners = {}; + this._value = ''; + this._unmaskedValue = ''; + this._saveSelection = this._saveSelection.bind(this); + this._onInput = this._onInput.bind(this); + this._onChange = this._onChange.bind(this); + this._onDrop = this._onDrop.bind(this); + this._onFocus = this._onFocus.bind(this); + this._onClick = this._onClick.bind(this); + this.alignCursor = this.alignCursor.bind(this); + this.alignCursorFriendly = this.alignCursorFriendly.bind(this); + this._bindEvents(); + + // refresh + this.updateValue(); + this._onChange(); + } + maskEquals(mask) { + var _this$masked; + return mask == null || ((_this$masked = this.masked) == null ? void 0 : _this$masked.maskEquals(mask)); + } + + /** Masked */ + get mask() { + return this.masked.mask; + } + set mask(mask) { + if (this.maskEquals(mask)) return; + if (!(mask instanceof IMask.Masked) && this.masked.constructor === maskedClass(mask)) { + // TODO "any" no idea + this.masked.updateOptions({ + mask + }); + return; + } + const masked = mask instanceof IMask.Masked ? mask : createMask({ + mask + }); + masked.unmaskedValue = this.masked.unmaskedValue; + this.masked = masked; + } + + /** Raw value */ + get value() { + return this._value; + } + set value(str) { + if (this.value === str) return; + this.masked.value = str; + this.updateControl(); + this.alignCursor(); + } + + /** Unmasked value */ + get unmaskedValue() { + return this._unmaskedValue; + } + set unmaskedValue(str) { + if (this.unmaskedValue === str) return; + this.masked.unmaskedValue = str; + this.updateControl(); + this.alignCursor(); + } + + /** Typed unmasked value */ + get typedValue() { + return this.masked.typedValue; + } + set typedValue(val) { + if (this.masked.typedValueEquals(val)) return; + this.masked.typedValue = val; + this.updateControl(); + this.alignCursor(); + } + + /** Display value */ + get displayValue() { + return this.masked.displayValue; + } + + /** Starts listening to element events */ + _bindEvents() { + this.el.bindEvents({ + selectionChange: this._saveSelection, + input: this._onInput, + drop: this._onDrop, + click: this._onClick, + focus: this._onFocus, + commit: this._onChange + }); + } + + /** Stops listening to element events */ + _unbindEvents() { + if (this.el) this.el.unbindEvents(); + } + + /** Fires custom event */ + _fireEvent(ev, e) { + const listeners = this._listeners[ev]; + if (!listeners) return; + listeners.forEach(l => l(e)); + } + + /** Current selection start */ + get selectionStart() { + return this._cursorChanging ? this._changingCursorPos : this.el.selectionStart; + } + + /** Current cursor position */ + get cursorPos() { + return this._cursorChanging ? this._changingCursorPos : this.el.selectionEnd; + } + set cursorPos(pos) { + if (!this.el || !this.el.isActive) return; + this.el.select(pos, pos); + this._saveSelection(); + } + + /** Stores current selection */ + _saveSelection( /* ev */ + ) { + if (this.displayValue !== this.el.value) { + console.warn('Element value was changed outside of mask. Syncronize mask using `mask.updateValue()` to work properly.'); // eslint-disable-line no-console + } + + this._selection = { + start: this.selectionStart, + end: this.cursorPos + }; + } + + /** Syncronizes model value from view */ + updateValue() { + this.masked.value = this.el.value; + this._value = this.masked.value; + } + + /** Syncronizes view from model value, fires change events */ + updateControl() { + const newUnmaskedValue = this.masked.unmaskedValue; + const newValue = this.masked.value; + const newDisplayValue = this.displayValue; + const isChanged = this.unmaskedValue !== newUnmaskedValue || this.value !== newValue; + this._unmaskedValue = newUnmaskedValue; + this._value = newValue; + if (this.el.value !== newDisplayValue) this.el.value = newDisplayValue; + if (isChanged) this._fireChangeEvents(); + } + + /** Updates options with deep equal check, recreates {@link Masked} model if mask type changes */ + updateOptions(opts) { + const { + mask, + ...restOpts + } = opts; + const updateMask = !this.maskEquals(mask); + const updateOpts = !objectIncludes(this.masked, restOpts); + if (updateMask) this.mask = mask; + if (updateOpts) this.masked.updateOptions(restOpts); // TODO + + if (updateMask || updateOpts) this.updateControl(); + } + + /** Updates cursor */ + updateCursor(cursorPos) { + if (cursorPos == null) return; + this.cursorPos = cursorPos; + + // also queue change cursor for mobile browsers + this._delayUpdateCursor(cursorPos); + } + + /** Delays cursor update to support mobile browsers */ + _delayUpdateCursor(cursorPos) { + this._abortUpdateCursor(); + this._changingCursorPos = cursorPos; + this._cursorChanging = setTimeout(() => { + if (!this.el) return; // if was destroyed + this.cursorPos = this._changingCursorPos; + this._abortUpdateCursor(); + }, 10); + } + + /** Fires custom events */ + _fireChangeEvents() { + this._fireEvent('accept', this._inputEvent); + if (this.masked.isComplete) this._fireEvent('complete', this._inputEvent); + } + + /** Aborts delayed cursor update */ + _abortUpdateCursor() { + if (this._cursorChanging) { + clearTimeout(this._cursorChanging); + delete this._cursorChanging; + } + } + + /** Aligns cursor to nearest available position */ + alignCursor() { + this.cursorPos = this.masked.nearestInputPos(this.masked.nearestInputPos(this.cursorPos, DIRECTION.LEFT)); + } + + /** Aligns cursor only if selection is empty */ + alignCursorFriendly() { + if (this.selectionStart !== this.cursorPos) return; // skip if range is selected + this.alignCursor(); + } + + /** Adds listener on custom event */ + on(ev, handler) { + if (!this._listeners[ev]) this._listeners[ev] = []; + this._listeners[ev].push(handler); + return this; + } + + /** Removes custom event listener */ + off(ev, handler) { + if (!this._listeners[ev]) return this; + if (!handler) { + delete this._listeners[ev]; + return this; + } + const hIndex = this._listeners[ev].indexOf(handler); + if (hIndex >= 0) this._listeners[ev].splice(hIndex, 1); + return this; + } + + /** Handles view input event */ + _onInput(e) { + this._inputEvent = e; + this._abortUpdateCursor(); + + // fix strange IE behavior + if (!this._selection) return this.updateValue(); + const details = new ActionDetails({ + // new state + value: this.el.value, + cursorPos: this.cursorPos, + // old state + oldValue: this.displayValue, + oldSelection: this._selection + }); + const oldRawValue = this.masked.rawInputValue; + const offset = this.masked.splice(details.startChangePos, details.removed.length, details.inserted, details.removeDirection, { + input: true, + raw: true + }).offset; + + // force align in remove direction only if no input chars were removed + // otherwise we still need to align with NONE (to get out from fixed symbols for instance) + const removeDirection = oldRawValue === this.masked.rawInputValue ? details.removeDirection : DIRECTION.NONE; + let cursorPos = this.masked.nearestInputPos(details.startChangePos + offset, removeDirection); + if (removeDirection !== DIRECTION.NONE) cursorPos = this.masked.nearestInputPos(cursorPos, DIRECTION.NONE); + this.updateControl(); + this.updateCursor(cursorPos); + delete this._inputEvent; + } + + /** Handles view change event and commits model value */ + _onChange() { + if (this.displayValue !== this.el.value) { + this.updateValue(); + } + this.masked.doCommit(); + this.updateControl(); + this._saveSelection(); + } + + /** Handles view drop event, prevents by default */ + _onDrop(ev) { + ev.preventDefault(); + ev.stopPropagation(); + } + + /** Restore last selection on focus */ + _onFocus(ev) { + this.alignCursorFriendly(); + } + + /** Restore last selection on focus */ + _onClick(ev) { + this.alignCursorFriendly(); + } + + /** Unbind view events and removes element reference */ + destroy() { + this._unbindEvents(); + this._listeners.length = 0; + delete this.el; + } + } + IMask.InputMask = InputMask; + + /** Provides details of changing model value */ + class ChangeDetails { + /** Inserted symbols */ + + /** Can skip chars */ + + /** Additional offset if any changes occurred before tail */ + + /** Raw inserted is used by dynamic mask */ + + static normalize(prep) { + return Array.isArray(prep) ? prep : [prep, new ChangeDetails()]; + } + constructor(details) { + Object.assign(this, { + inserted: '', + rawInserted: '', + skip: false, + tailShift: 0 + }, details); + } + + /** Aggregate changes */ + aggregate(details) { + this.rawInserted += details.rawInserted; + this.skip = this.skip || details.skip; + this.inserted += details.inserted; + this.tailShift += details.tailShift; + return this; + } + + /** Total offset considering all changes */ + get offset() { + return this.tailShift + this.inserted.length; + } + } + IMask.ChangeDetails = ChangeDetails; + + /** Provides details of continuous extracted tail */ + class ContinuousTailDetails { + /** Tail value as string */ + + /** Tail start position */ + + /** Start position */ + + constructor(value, from, stop) { + if (value === void 0) { + value = ''; + } + if (from === void 0) { + from = 0; + } + this.value = value; + this.from = from; + this.stop = stop; + } + toString() { + return this.value; + } + extend(tail) { + this.value += String(tail); + } + appendTo(masked) { + return masked.append(this.toString(), { + tail: true + }).aggregate(masked._appendPlaceholder()); + } + get state() { + return { + value: this.value, + from: this.from, + stop: this.stop + }; + } + set state(state) { + Object.assign(this, state); + } + unshift(beforePos) { + if (!this.value.length || beforePos != null && this.from >= beforePos) return ''; + const shiftChar = this.value[0]; + this.value = this.value.slice(1); + return shiftChar; + } + shift() { + if (!this.value.length) return ''; + const shiftChar = this.value[this.value.length - 1]; + this.value = this.value.slice(0, -1); + return shiftChar; + } + } + + /** Append flags */ + + /** Extract flags */ + + // see https://github.com/microsoft/TypeScript/issues/6223 + + /** Provides common masking stuff */ + class Masked { + /** */ + + /** */ + + /** Transforms value before mask processing */ + + /** Transforms each char before mask processing */ + + /** Validates if value is acceptable */ + + /** Does additional processing at the end of editing */ + + /** Format typed value to string */ + + /** Parse string to get typed value */ + + /** Enable characters overwriting */ + + /** */ + + /** */ + + /** */ + + constructor(opts) { + this._value = ''; + this._update({ + ...Masked.DEFAULTS, + ...opts + }); + this._initialized = true; + } + + /** Sets and applies new options */ + updateOptions(opts) { + if (!Object.keys(opts).length) return; + this.withValueRefresh(this._update.bind(this, opts)); + } + + /** Sets new options */ + _update(opts) { + Object.assign(this, opts); + } + + /** Mask state */ + get state() { + return { + _value: this.value, + _rawInputValue: this.rawInputValue + }; + } + set state(state) { + this._value = state._value; + } + + /** Resets value */ + reset() { + this._value = ''; + } + get value() { + return this._value; + } + set value(value) { + this.resolve(value, { + input: true + }); + } + + /** Resolve new value */ + resolve(value, flags) { + if (flags === void 0) { + flags = { + input: true + }; + } + this.reset(); + this.append(value, flags, ''); + this.doCommit(); + } + get unmaskedValue() { + return this.value; + } + set unmaskedValue(value) { + this.resolve(value, {}); + } + get typedValue() { + return this.parse ? this.parse(this.value, this) : this.unmaskedValue; + } + set typedValue(value) { + if (this.format) { + this.value = this.format(value, this); + } else { + this.unmaskedValue = String(value); + } + } + + /** Value that includes raw user input */ + get rawInputValue() { + return this.extractInput(0, this.displayValue.length, { + raw: true + }); + } + set rawInputValue(value) { + this.resolve(value, { + raw: true + }); + } + get displayValue() { + return this.value; + } + get isComplete() { + return true; + } + get isFilled() { + return this.isComplete; + } + + /** Finds nearest input position in direction */ + nearestInputPos(cursorPos, direction) { + return cursorPos; + } + totalInputPositions(fromPos, toPos) { + if (fromPos === void 0) { + fromPos = 0; + } + if (toPos === void 0) { + toPos = this.displayValue.length; + } + return Math.min(this.displayValue.length, toPos - fromPos); + } + + /** Extracts value in range considering flags */ + extractInput(fromPos, toPos, flags) { + if (fromPos === void 0) { + fromPos = 0; + } + if (toPos === void 0) { + toPos = this.displayValue.length; + } + return this.displayValue.slice(fromPos, toPos); + } + + /** Extracts tail in range */ + extractTail(fromPos, toPos) { + if (fromPos === void 0) { + fromPos = 0; + } + if (toPos === void 0) { + toPos = this.displayValue.length; + } + return new ContinuousTailDetails(this.extractInput(fromPos, toPos), fromPos); + } + + /** Appends tail */ + appendTail(tail) { + if (isString(tail)) tail = new ContinuousTailDetails(String(tail)); + return tail.appendTo(this); + } + + /** Appends char */ + _appendCharRaw(ch, flags) { + if (!ch) return new ChangeDetails(); + this._value += ch; + return new ChangeDetails({ + inserted: ch, + rawInserted: ch + }); + } + + /** Appends char */ + _appendChar(ch, flags, checkTail) { + if (flags === void 0) { + flags = {}; + } + const consistentState = this.state; + let details; + [ch, details] = this.doPrepareChar(ch, flags); + details = details.aggregate(this._appendCharRaw(ch, flags)); + if (details.inserted) { + let consistentTail; + let appended = this.doValidate(flags) !== false; + if (appended && checkTail != null) { + // validation ok, check tail + const beforeTailState = this.state; + if (this.overwrite === true) { + consistentTail = checkTail.state; + checkTail.unshift(this.displayValue.length - details.tailShift); + } + let tailDetails = this.appendTail(checkTail); + appended = tailDetails.rawInserted === checkTail.toString(); + + // not ok, try shift + if (!(appended && tailDetails.inserted) && this.overwrite === 'shift') { + this.state = beforeTailState; + consistentTail = checkTail.state; + checkTail.shift(); + tailDetails = this.appendTail(checkTail); + appended = tailDetails.rawInserted === checkTail.toString(); + } + + // if ok, rollback state after tail + if (appended && tailDetails.inserted) this.state = beforeTailState; + } + + // revert all if something went wrong + if (!appended) { + details = new ChangeDetails(); + this.state = consistentState; + if (checkTail && consistentTail) checkTail.state = consistentTail; + } + } + return details; + } + + /** Appends optional placeholder at the end */ + _appendPlaceholder() { + return new ChangeDetails(); + } + + /** Appends optional eager placeholder at the end */ + _appendEager() { + return new ChangeDetails(); + } + + /** Appends symbols considering flags */ + append(str, flags, tail) { + if (!isString(str)) throw new Error('value should be string'); + const checkTail = isString(tail) ? new ContinuousTailDetails(String(tail)) : tail; + if (flags != null && flags.tail) flags._beforeTailState = this.state; + let details; + [str, details] = this.doPrepare(str, flags); + for (let ci = 0; ci < str.length; ++ci) { + const d = this._appendChar(str[ci], flags, checkTail); + if (!d.rawInserted && !this.doSkipInvalid(str[ci], flags, checkTail)) break; + details.aggregate(d); + } + if ((this.eager === true || this.eager === 'append') && flags != null && flags.input && str) { + details.aggregate(this._appendEager()); + } + + // append tail but aggregate only tailShift + if (checkTail != null) { + details.tailShift += this.appendTail(checkTail).tailShift; + // TODO it's a good idea to clear state after appending ends + // but it causes bugs when one append calls another (when dynamic dispatch set rawInputValue) + // this._resetBeforeTailState(); + } + + return details; + } + remove(fromPos, toPos) { + if (fromPos === void 0) { + fromPos = 0; + } + if (toPos === void 0) { + toPos = this.displayValue.length; + } + this._value = this.displayValue.slice(0, fromPos) + this.displayValue.slice(toPos); + return new ChangeDetails(); + } + + /** Calls function and reapplies current value */ + withValueRefresh(fn) { + if (this._refreshing || !this._initialized) return fn(); + this._refreshing = true; + const rawInput = this.rawInputValue; + const value = this.value; + const ret = fn(); + this.rawInputValue = rawInput; + // append lost trailing chars at the end + if (this.value && this.value !== value && value.indexOf(this.value) === 0) { + this.append(value.slice(this.displayValue.length), {}, ''); + } + delete this._refreshing; + return ret; + } + runIsolated(fn) { + if (this._isolated || !this._initialized) return fn(this); + this._isolated = true; + const state = this.state; + const ret = fn(this); + this.state = state; + delete this._isolated; + return ret; + } + doSkipInvalid(ch, flags, checkTail) { + return Boolean(this.skipInvalid); + } + + /** Prepares string before mask processing */ + doPrepare(str, flags) { + if (flags === void 0) { + flags = {}; + } + return ChangeDetails.normalize(this.prepare ? this.prepare(str, this, flags) : str); + } + + /** Prepares each char before mask processing */ + doPrepareChar(str, flags) { + if (flags === void 0) { + flags = {}; + } + return ChangeDetails.normalize(this.prepareChar ? this.prepareChar(str, this, flags) : str); + } + + /** Validates if value is acceptable */ + doValidate(flags) { + return (!this.validate || this.validate(this.value, this, flags)) && (!this.parent || this.parent.doValidate(flags)); + } + + /** Does additional processing at the end of editing */ + doCommit() { + if (this.commit) this.commit(this.value, this); + } + splice(start, deleteCount, inserted, removeDirection, flags) { + if (removeDirection === void 0) { + removeDirection = DIRECTION.NONE; + } + if (flags === void 0) { + flags = { + input: true + }; + } + const tailPos = start + deleteCount; + const tail = this.extractTail(tailPos); + const eagerRemove = this.eager === true || this.eager === 'remove'; + let oldRawValue; + if (eagerRemove) { + removeDirection = forceDirection(removeDirection); + oldRawValue = this.extractInput(0, tailPos, { + raw: true + }); + } + let startChangePos = start; + const details = new ChangeDetails(); + + // if it is just deletion without insertion + if (removeDirection !== DIRECTION.NONE) { + startChangePos = this.nearestInputPos(start, deleteCount > 1 && start !== 0 && !eagerRemove ? DIRECTION.NONE : removeDirection); + + // adjust tailShift if start was aligned + details.tailShift = startChangePos - start; + } + details.aggregate(this.remove(startChangePos)); + if (eagerRemove && removeDirection !== DIRECTION.NONE && oldRawValue === this.rawInputValue) { + if (removeDirection === DIRECTION.FORCE_LEFT) { + let valLength; + while (oldRawValue === this.rawInputValue && (valLength = this.displayValue.length)) { + details.aggregate(new ChangeDetails({ + tailShift: -1 + })).aggregate(this.remove(valLength - 1)); + } + } else if (removeDirection === DIRECTION.FORCE_RIGHT) { + tail.unshift(); + } + } + return details.aggregate(this.append(inserted, flags, tail)); + } + maskEquals(mask) { + return this.mask === mask; + } + typedValueEquals(value) { + const tval = this.typedValue; + return value === tval || Masked.EMPTY_VALUES.includes(value) && Masked.EMPTY_VALUES.includes(tval) || (this.format ? this.format(value, this) === this.format(this.typedValue, this) : false); + } + } + Masked.DEFAULTS = { + skipInvalid: true + }; + Masked.EMPTY_VALUES = [undefined, null, '']; + IMask.Masked = Masked; + + class ChunksTailDetails { + /** */ + + constructor(chunks, from) { + if (chunks === void 0) { + chunks = []; + } + if (from === void 0) { + from = 0; + } + this.chunks = chunks; + this.from = from; + } + toString() { + return this.chunks.map(String).join(''); + } + extend(tailChunk) { + if (!String(tailChunk)) return; + tailChunk = isString(tailChunk) ? new ContinuousTailDetails(String(tailChunk)) : tailChunk; + const lastChunk = this.chunks[this.chunks.length - 1]; + const extendLast = lastChunk && ( + // if stops are same or tail has no stop + lastChunk.stop === tailChunk.stop || tailChunk.stop == null) && + // if tail chunk goes just after last chunk + tailChunk.from === lastChunk.from + lastChunk.toString().length; + if (tailChunk instanceof ContinuousTailDetails) { + // check the ability to extend previous chunk + if (extendLast) { + // extend previous chunk + lastChunk.extend(tailChunk.toString()); + } else { + // append new chunk + this.chunks.push(tailChunk); + } + } else if (tailChunk instanceof ChunksTailDetails) { + if (tailChunk.stop == null) { + // unwrap floating chunks to parent, keeping `from` pos + let firstTailChunk; + while (tailChunk.chunks.length && tailChunk.chunks[0].stop == null) { + firstTailChunk = tailChunk.chunks.shift(); // not possible to be `undefined` because length was checked above + firstTailChunk.from += tailChunk.from; + this.extend(firstTailChunk); + } + } + + // if tail chunk still has value + if (tailChunk.toString()) { + // if chunks contains stops, then popup stop to container + tailChunk.stop = tailChunk.blockIndex; + this.chunks.push(tailChunk); + } + } + } + appendTo(masked) { + if (!(masked instanceof IMask.MaskedPattern)) { + const tail = new ContinuousTailDetails(this.toString()); + return tail.appendTo(masked); + } + const details = new ChangeDetails(); + for (let ci = 0; ci < this.chunks.length && !details.skip; ++ci) { + const chunk = this.chunks[ci]; + const lastBlockIter = masked._mapPosToBlock(masked.displayValue.length); + const stop = chunk.stop; + let chunkBlock; + if (stop != null && ( + // if block not found or stop is behind lastBlock + !lastBlockIter || lastBlockIter.index <= stop)) { + if (chunk instanceof ChunksTailDetails || + // for continuous block also check if stop is exist + masked._stops.indexOf(stop) >= 0) { + const phDetails = masked._appendPlaceholder(stop); + details.aggregate(phDetails); + } + chunkBlock = chunk instanceof ChunksTailDetails && masked._blocks[stop]; + } + if (chunkBlock) { + const tailDetails = chunkBlock.appendTail(chunk); + tailDetails.skip = false; // always ignore skip, it will be set on last + details.aggregate(tailDetails); + masked._value += tailDetails.inserted; + + // get not inserted chars + const remainChars = chunk.toString().slice(tailDetails.rawInserted.length); + if (remainChars) details.aggregate(masked.append(remainChars, { + tail: true + })); + } else { + details.aggregate(masked.append(chunk.toString(), { + tail: true + })); + } + } + return details; + } + get state() { + return { + chunks: this.chunks.map(c => c.state), + from: this.from, + stop: this.stop, + blockIndex: this.blockIndex + }; + } + set state(state) { + const { + chunks, + ...props + } = state; + Object.assign(this, props); + this.chunks = chunks.map(cstate => { + const chunk = "chunks" in cstate ? new ChunksTailDetails() : new ContinuousTailDetails(); + chunk.state = cstate; + return chunk; + }); + } + unshift(beforePos) { + if (!this.chunks.length || beforePos != null && this.from >= beforePos) return ''; + const chunkShiftPos = beforePos != null ? beforePos - this.from : beforePos; + let ci = 0; + while (ci < this.chunks.length) { + const chunk = this.chunks[ci]; + const shiftChar = chunk.unshift(chunkShiftPos); + if (chunk.toString()) { + // chunk still contains value + // but not shifted - means no more available chars to shift + if (!shiftChar) break; + ++ci; + } else { + // clean if chunk has no value + this.chunks.splice(ci, 1); + } + if (shiftChar) return shiftChar; + } + return ''; + } + shift() { + if (!this.chunks.length) return ''; + let ci = this.chunks.length - 1; + while (0 <= ci) { + const chunk = this.chunks[ci]; + const shiftChar = chunk.shift(); + if (chunk.toString()) { + // chunk still contains value + // but not shifted - means no more available chars to shift + if (!shiftChar) break; + --ci; + } else { + // clean if chunk has no value + this.chunks.splice(ci, 1); + } + if (shiftChar) return shiftChar; + } + return ''; + } + } + + class PatternCursor { + constructor(masked, pos) { + this.masked = masked; + this._log = []; + const { + offset, + index + } = masked._mapPosToBlock(pos) || (pos < 0 ? + // first + { + index: 0, + offset: 0 + } : + // last + { + index: this.masked._blocks.length, + offset: 0 + }); + this.offset = offset; + this.index = index; + this.ok = false; + } + get block() { + return this.masked._blocks[this.index]; + } + get pos() { + return this.masked._blockStartPos(this.index) + this.offset; + } + get state() { + return { + index: this.index, + offset: this.offset, + ok: this.ok + }; + } + set state(s) { + Object.assign(this, s); + } + pushState() { + this._log.push(this.state); + } + popState() { + const s = this._log.pop(); + if (s) this.state = s; + return s; + } + bindBlock() { + if (this.block) return; + if (this.index < 0) { + this.index = 0; + this.offset = 0; + } + if (this.index >= this.masked._blocks.length) { + this.index = this.masked._blocks.length - 1; + this.offset = this.block.displayValue.length; // TODO this is stupid type error, `block` depends on index that was changed above + } + } + + _pushLeft(fn) { + this.pushState(); + for (this.bindBlock(); 0 <= this.index; --this.index, this.offset = ((_this$block = this.block) == null ? void 0 : _this$block.displayValue.length) || 0) { + var _this$block; + if (fn()) return this.ok = true; + } + return this.ok = false; + } + _pushRight(fn) { + this.pushState(); + for (this.bindBlock(); this.index < this.masked._blocks.length; ++this.index, this.offset = 0) { + if (fn()) return this.ok = true; + } + return this.ok = false; + } + pushLeftBeforeFilled() { + return this._pushLeft(() => { + if (this.block.isFixed || !this.block.value) return; + this.offset = this.block.nearestInputPos(this.offset, DIRECTION.FORCE_LEFT); + if (this.offset !== 0) return true; + }); + } + pushLeftBeforeInput() { + // cases: + // filled input: 00| + // optional empty input: 00[]| + // nested block: XX<[]>| + return this._pushLeft(() => { + if (this.block.isFixed) return; + this.offset = this.block.nearestInputPos(this.offset, DIRECTION.LEFT); + return true; + }); + } + pushLeftBeforeRequired() { + return this._pushLeft(() => { + if (this.block.isFixed || this.block.isOptional && !this.block.value) return; + this.offset = this.block.nearestInputPos(this.offset, DIRECTION.LEFT); + return true; + }); + } + pushRightBeforeFilled() { + return this._pushRight(() => { + if (this.block.isFixed || !this.block.value) return; + this.offset = this.block.nearestInputPos(this.offset, DIRECTION.FORCE_RIGHT); + if (this.offset !== this.block.value.length) return true; + }); + } + pushRightBeforeInput() { + return this._pushRight(() => { + if (this.block.isFixed) return; + + // const o = this.offset; + this.offset = this.block.nearestInputPos(this.offset, DIRECTION.NONE); + // HACK cases like (STILL DOES NOT WORK FOR NESTED) + // aa|X + // aaX_ - this will not work + // if (o && o === this.offset && this.block instanceof PatternInputDefinition) continue; + return true; + }); + } + pushRightBeforeRequired() { + return this._pushRight(() => { + if (this.block.isFixed || this.block.isOptional && !this.block.value) return; + + // TODO check |[*]XX_ + this.offset = this.block.nearestInputPos(this.offset, DIRECTION.NONE); + return true; + }); + } + } + + class PatternFixedDefinition { + /** */ + + /** */ + + /** */ + + /** */ + + /** */ + + /** */ + + constructor(opts) { + Object.assign(this, opts); + this._value = ''; + this.isFixed = true; + } + get value() { + return this._value; + } + get unmaskedValue() { + return this.isUnmasking ? this.value : ''; + } + get rawInputValue() { + return this._isRawInput ? this.value : ''; + } + get displayValue() { + return this.value; + } + reset() { + this._isRawInput = false; + this._value = ''; + } + remove(fromPos, toPos) { + if (fromPos === void 0) { + fromPos = 0; + } + if (toPos === void 0) { + toPos = this._value.length; + } + this._value = this._value.slice(0, fromPos) + this._value.slice(toPos); + if (!this._value) this._isRawInput = false; + return new ChangeDetails(); + } + nearestInputPos(cursorPos, direction) { + if (direction === void 0) { + direction = DIRECTION.NONE; + } + const minPos = 0; + const maxPos = this._value.length; + switch (direction) { + case DIRECTION.LEFT: + case DIRECTION.FORCE_LEFT: + return minPos; + case DIRECTION.NONE: + case DIRECTION.RIGHT: + case DIRECTION.FORCE_RIGHT: + default: + return maxPos; + } + } + totalInputPositions(fromPos, toPos) { + if (fromPos === void 0) { + fromPos = 0; + } + if (toPos === void 0) { + toPos = this._value.length; + } + return this._isRawInput ? toPos - fromPos : 0; + } + extractInput(fromPos, toPos, flags) { + if (fromPos === void 0) { + fromPos = 0; + } + if (toPos === void 0) { + toPos = this._value.length; + } + if (flags === void 0) { + flags = {}; + } + return flags.raw && this._isRawInput && this._value.slice(fromPos, toPos) || ''; + } + get isComplete() { + return true; + } + get isFilled() { + return Boolean(this._value); + } + _appendChar(ch, flags) { + if (flags === void 0) { + flags = {}; + } + const details = new ChangeDetails(); + if (this.isFilled) return details; + const appendEager = this.eager === true || this.eager === 'append'; + const appended = this.char === ch; + const isResolved = appended && (this.isUnmasking || flags.input || flags.raw) && (!flags.raw || !appendEager) && !flags.tail; + if (isResolved) details.rawInserted = this.char; + this._value = details.inserted = this.char; + this._isRawInput = isResolved && (flags.raw || flags.input); + return details; + } + _appendEager() { + return this._appendChar(this.char, { + tail: true + }); + } + _appendPlaceholder() { + const details = new ChangeDetails(); + if (this.isFilled) return details; + this._value = details.inserted = this.char; + return details; + } + extractTail() { + return new ContinuousTailDetails(''); + } + appendTail(tail) { + if (isString(tail)) tail = new ContinuousTailDetails(String(tail)); + return tail.appendTo(this); + } + append(str, flags, tail) { + const details = this._appendChar(str[0], flags); + if (tail != null) { + details.tailShift += this.appendTail(tail).tailShift; + } + return details; + } + doCommit() {} + get state() { + return { + _value: this._value, + _rawInputValue: this.rawInputValue + }; + } + set state(state) { + this._value = state._value; + this._isRawInput = Boolean(state._rawInputValue); + } + } + + class PatternInputDefinition { + /** */ + + /** */ + + /** */ + + /** */ + + /** */ + + /** */ + + /** */ + + /** */ + + constructor(opts) { + const { + parent, + isOptional, + placeholderChar, + displayChar, + lazy, + eager, + ...maskOpts + } = opts; + this.masked = createMask(maskOpts); + Object.assign(this, { + parent, + isOptional, + placeholderChar, + displayChar, + lazy, + eager + }); + } + reset() { + this.isFilled = false; + this.masked.reset(); + } + remove(fromPos, toPos) { + if (fromPos === void 0) { + fromPos = 0; + } + if (toPos === void 0) { + toPos = this.value.length; + } + if (fromPos === 0 && toPos >= 1) { + this.isFilled = false; + return this.masked.remove(fromPos, toPos); + } + return new ChangeDetails(); + } + get value() { + return this.masked.value || (this.isFilled && !this.isOptional ? this.placeholderChar : ''); + } + get unmaskedValue() { + return this.masked.unmaskedValue; + } + get rawInputValue() { + return this.masked.rawInputValue; + } + get displayValue() { + return this.masked.value && this.displayChar || this.value; + } + get isComplete() { + return Boolean(this.masked.value) || this.isOptional; + } + _appendChar(ch, flags) { + if (flags === void 0) { + flags = {}; + } + if (this.isFilled) return new ChangeDetails(); + const state = this.masked.state; + // simulate input + const details = this.masked._appendChar(ch, this.currentMaskFlags(flags)); + if (details.inserted && this.doValidate(flags) === false) { + details.inserted = details.rawInserted = ''; + this.masked.state = state; + } + if (!details.inserted && !this.isOptional && !this.lazy && !flags.input) { + details.inserted = this.placeholderChar; + } + details.skip = !details.inserted && !this.isOptional; + this.isFilled = Boolean(details.inserted); + return details; + } + append(str, flags, tail) { + // TODO probably should be done via _appendChar + return this.masked.append(str, this.currentMaskFlags(flags), tail); + } + _appendPlaceholder() { + const details = new ChangeDetails(); + if (this.isFilled || this.isOptional) return details; + this.isFilled = true; + details.inserted = this.placeholderChar; + return details; + } + _appendEager() { + return new ChangeDetails(); + } + extractTail(fromPos, toPos) { + return this.masked.extractTail(fromPos, toPos); + } + appendTail(tail) { + return this.masked.appendTail(tail); + } + extractInput(fromPos, toPos, flags) { + if (fromPos === void 0) { + fromPos = 0; + } + if (toPos === void 0) { + toPos = this.value.length; + } + return this.masked.extractInput(fromPos, toPos, flags); + } + nearestInputPos(cursorPos, direction) { + if (direction === void 0) { + direction = DIRECTION.NONE; + } + const minPos = 0; + const maxPos = this.value.length; + const boundPos = Math.min(Math.max(cursorPos, minPos), maxPos); + switch (direction) { + case DIRECTION.LEFT: + case DIRECTION.FORCE_LEFT: + return this.isComplete ? boundPos : minPos; + case DIRECTION.RIGHT: + case DIRECTION.FORCE_RIGHT: + return this.isComplete ? boundPos : maxPos; + case DIRECTION.NONE: + default: + return boundPos; + } + } + totalInputPositions(fromPos, toPos) { + if (fromPos === void 0) { + fromPos = 0; + } + if (toPos === void 0) { + toPos = this.value.length; + } + return this.value.slice(fromPos, toPos).length; + } + doValidate(flags) { + return this.masked.doValidate(this.currentMaskFlags(flags)) && (!this.parent || this.parent.doValidate(this.currentMaskFlags(flags))); + } + doCommit() { + this.masked.doCommit(); + } + get state() { + return { + _value: this.value, + _rawInputValue: this.rawInputValue, + masked: this.masked.state, + isFilled: this.isFilled + }; + } + set state(state) { + this.masked.state = state.masked; + this.isFilled = state.isFilled; + } + currentMaskFlags(flags) { + var _flags$_beforeTailSta; + return { + ...flags, + _beforeTailState: (flags == null ? void 0 : (_flags$_beforeTailSta = flags._beforeTailState) == null ? void 0 : _flags$_beforeTailSta.masked) || (flags == null ? void 0 : flags._beforeTailState) + }; + } + } + PatternInputDefinition.DEFAULT_DEFINITIONS = { + '0': /\d/, + 'a': /[\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/, + // http://stackoverflow.com/a/22075070 + '*': /./ + }; + + /** Masking by RegExp */ + class MaskedRegExp extends Masked { + /** */ + + /** Enable characters overwriting */ + + /** */ + + /** */ + + updateOptions(opts) { + super.updateOptions(opts); + } + _update(opts) { + const mask = opts.mask; + if (mask) opts.validate = value => value.search(mask) >= 0; + super._update(opts); + } + } + IMask.MaskedRegExp = MaskedRegExp; + + /** Pattern mask */ + class MaskedPattern extends Masked { + /** */ + + /** */ + + /** Single char for empty input */ + + /** Single char for filled input */ + + /** Show placeholder only when needed */ + + /** Enable characters overwriting */ + + /** */ + + /** */ + + constructor(opts) { + super({ + ...MaskedPattern.DEFAULTS, + ...opts, + definitions: Object.assign({}, PatternInputDefinition.DEFAULT_DEFINITIONS, opts == null ? void 0 : opts.definitions) + }); + } + updateOptions(opts) { + super.updateOptions(opts); + } + _update(opts) { + opts.definitions = Object.assign({}, this.definitions, opts.definitions); + super._update(opts); + this._rebuildMask(); + } + _rebuildMask() { + const defs = this.definitions; + this._blocks = []; + this.exposeBlock = undefined; + this._stops = []; + this._maskedBlocks = {}; + const pattern = this.mask; + if (!pattern || !defs) return; + let unmaskingBlock = false; + let optionalBlock = false; + for (let i = 0; i < pattern.length; ++i) { + if (this.blocks) { + const p = pattern.slice(i); + const bNames = Object.keys(this.blocks).filter(bName => p.indexOf(bName) === 0); + // order by key length + bNames.sort((a, b) => b.length - a.length); + // use block name with max length + const bName = bNames[0]; + if (bName) { + const { + expose, + ...blockOpts + } = normalizeOpts(this.blocks[bName]); + const maskedBlock = createMask({ + lazy: this.lazy, + eager: this.eager, + placeholderChar: this.placeholderChar, + displayChar: this.displayChar, + overwrite: this.overwrite, + ...blockOpts, + parent: this + }); + if (maskedBlock) { + this._blocks.push(maskedBlock); + if (expose) this.exposeBlock = maskedBlock; + + // store block index + if (!this._maskedBlocks[bName]) this._maskedBlocks[bName] = []; + this._maskedBlocks[bName].push(this._blocks.length - 1); + } + i += bName.length - 1; + continue; + } + } + let char = pattern[i]; + let isInput = (char in defs); + if (char === MaskedPattern.STOP_CHAR) { + this._stops.push(this._blocks.length); + continue; + } + if (char === '{' || char === '}') { + unmaskingBlock = !unmaskingBlock; + continue; + } + if (char === '[' || char === ']') { + optionalBlock = !optionalBlock; + continue; + } + if (char === MaskedPattern.ESCAPE_CHAR) { + ++i; + char = pattern[i]; + if (!char) break; + isInput = false; + } + const def = isInput ? new PatternInputDefinition({ + isOptional: optionalBlock, + lazy: this.lazy, + eager: this.eager, + placeholderChar: this.placeholderChar, + displayChar: this.displayChar, + ...normalizeOpts(defs[char]), + parent: this + }) : new PatternFixedDefinition({ + char, + eager: this.eager, + isUnmasking: unmaskingBlock + }); + this._blocks.push(def); + } + } + get state() { + return { + ...super.state, + _blocks: this._blocks.map(b => b.state) + }; + } + set state(state) { + const { + _blocks, + ...maskedState + } = state; + this._blocks.forEach((b, bi) => b.state = _blocks[bi]); + super.state = maskedState; + } + reset() { + super.reset(); + this._blocks.forEach(b => b.reset()); + } + get isComplete() { + return this.exposeBlock ? this.exposeBlock.isComplete : this._blocks.every(b => b.isComplete); + } + get isFilled() { + return this._blocks.every(b => b.isFilled); + } + get isFixed() { + return this._blocks.every(b => b.isFixed); + } + get isOptional() { + return this._blocks.every(b => b.isOptional); + } + doCommit() { + this._blocks.forEach(b => b.doCommit()); + super.doCommit(); + } + get unmaskedValue() { + return this.exposeBlock ? this.exposeBlock.unmaskedValue : this._blocks.reduce((str, b) => str += b.unmaskedValue, ''); + } + set unmaskedValue(unmaskedValue) { + if (this.exposeBlock) { + const tail = this.extractTail(this._blockStartPos(this._blocks.indexOf(this.exposeBlock)) + this.exposeBlock.displayValue.length); + this.exposeBlock.unmaskedValue = unmaskedValue; + this.appendTail(tail); + this.doCommit(); + } else super.unmaskedValue = unmaskedValue; + } + get value() { + return this.exposeBlock ? this.exposeBlock.value : + // TODO return _value when not in change? + this._blocks.reduce((str, b) => str += b.value, ''); + } + set value(value) { + if (this.exposeBlock) { + const tail = this.extractTail(this._blockStartPos(this._blocks.indexOf(this.exposeBlock)) + this.exposeBlock.displayValue.length); + this.exposeBlock.value = value; + this.appendTail(tail); + this.doCommit(); + } else super.value = value; + } + get typedValue() { + return this.exposeBlock ? this.exposeBlock.typedValue : super.typedValue; + } + set typedValue(value) { + if (this.exposeBlock) { + const tail = this.extractTail(this._blockStartPos(this._blocks.indexOf(this.exposeBlock)) + this.exposeBlock.displayValue.length); + this.exposeBlock.typedValue = value; + this.appendTail(tail); + this.doCommit(); + } else super.typedValue = value; + } + get displayValue() { + return this._blocks.reduce((str, b) => str += b.displayValue, ''); + } + appendTail(tail) { + return super.appendTail(tail).aggregate(this._appendPlaceholder()); + } + _appendEager() { + var _this$_mapPosToBlock; + const details = new ChangeDetails(); + let startBlockIndex = (_this$_mapPosToBlock = this._mapPosToBlock(this.displayValue.length)) == null ? void 0 : _this$_mapPosToBlock.index; + if (startBlockIndex == null) return details; + + // TODO test if it works for nested pattern masks + if (this._blocks[startBlockIndex].isFilled) ++startBlockIndex; + for (let bi = startBlockIndex; bi < this._blocks.length; ++bi) { + const d = this._blocks[bi]._appendEager(); + if (!d.inserted) break; + details.aggregate(d); + } + return details; + } + _appendCharRaw(ch, flags) { + if (flags === void 0) { + flags = {}; + } + const blockIter = this._mapPosToBlock(this.displayValue.length); + const details = new ChangeDetails(); + if (!blockIter) return details; + for (let bi = blockIter.index;; ++bi) { + var _flags$_beforeTailSta, _flags$_beforeTailSta2; + const block = this._blocks[bi]; + if (!block) break; + const blockDetails = block._appendChar(ch, { + ...flags, + _beforeTailState: (_flags$_beforeTailSta = flags._beforeTailState) == null ? void 0 : (_flags$_beforeTailSta2 = _flags$_beforeTailSta._blocks) == null ? void 0 : _flags$_beforeTailSta2[bi] + }); + const skip = blockDetails.skip; + details.aggregate(blockDetails); + if (skip || blockDetails.rawInserted) break; // go next char + } + + return details; + } + extractTail(fromPos, toPos) { + if (fromPos === void 0) { + fromPos = 0; + } + if (toPos === void 0) { + toPos = this.displayValue.length; + } + const chunkTail = new ChunksTailDetails(); + if (fromPos === toPos) return chunkTail; + this._forEachBlocksInRange(fromPos, toPos, (b, bi, bFromPos, bToPos) => { + const blockChunk = b.extractTail(bFromPos, bToPos); + blockChunk.stop = this._findStopBefore(bi); + blockChunk.from = this._blockStartPos(bi); + if (blockChunk instanceof ChunksTailDetails) blockChunk.blockIndex = bi; + chunkTail.extend(blockChunk); + }); + return chunkTail; + } + extractInput(fromPos, toPos, flags) { + if (fromPos === void 0) { + fromPos = 0; + } + if (toPos === void 0) { + toPos = this.displayValue.length; + } + if (flags === void 0) { + flags = {}; + } + if (fromPos === toPos) return ''; + let input = ''; + this._forEachBlocksInRange(fromPos, toPos, (b, _, fromPos, toPos) => { + input += b.extractInput(fromPos, toPos, flags); + }); + return input; + } + _findStopBefore(blockIndex) { + let stopBefore; + for (let si = 0; si < this._stops.length; ++si) { + const stop = this._stops[si]; + if (stop <= blockIndex) stopBefore = stop;else break; + } + return stopBefore; + } + + /** Appends placeholder depending on laziness */ + _appendPlaceholder(toBlockIndex) { + const details = new ChangeDetails(); + if (this.lazy && toBlockIndex == null) return details; + const startBlockIter = this._mapPosToBlock(this.displayValue.length); + if (!startBlockIter) return details; + const startBlockIndex = startBlockIter.index; + const endBlockIndex = toBlockIndex != null ? toBlockIndex : this._blocks.length; + this._blocks.slice(startBlockIndex, endBlockIndex).forEach(b => { + if (!b.lazy || toBlockIndex != null) { + var _blocks2; + const bDetails = b._appendPlaceholder((_blocks2 = b._blocks) == null ? void 0 : _blocks2.length); + this._value += bDetails.inserted; + details.aggregate(bDetails); + } + }); + return details; + } + + /** Finds block in pos */ + _mapPosToBlock(pos) { + let accVal = ''; + for (let bi = 0; bi < this._blocks.length; ++bi) { + const block = this._blocks[bi]; + const blockStartPos = accVal.length; + accVal += block.displayValue; + if (pos <= accVal.length) { + return { + index: bi, + offset: pos - blockStartPos + }; + } + } + } + _blockStartPos(blockIndex) { + return this._blocks.slice(0, blockIndex).reduce((pos, b) => pos += b.displayValue.length, 0); + } + _forEachBlocksInRange(fromPos, toPos, fn) { + if (toPos === void 0) { + toPos = this.displayValue.length; + } + const fromBlockIter = this._mapPosToBlock(fromPos); + if (fromBlockIter) { + const toBlockIter = this._mapPosToBlock(toPos); + // process first block + const isSameBlock = toBlockIter && fromBlockIter.index === toBlockIter.index; + const fromBlockStartPos = fromBlockIter.offset; + const fromBlockEndPos = toBlockIter && isSameBlock ? toBlockIter.offset : this._blocks[fromBlockIter.index].displayValue.length; + fn(this._blocks[fromBlockIter.index], fromBlockIter.index, fromBlockStartPos, fromBlockEndPos); + if (toBlockIter && !isSameBlock) { + // process intermediate blocks + for (let bi = fromBlockIter.index + 1; bi < toBlockIter.index; ++bi) { + fn(this._blocks[bi], bi, 0, this._blocks[bi].displayValue.length); + } + + // process last block + fn(this._blocks[toBlockIter.index], toBlockIter.index, 0, toBlockIter.offset); + } + } + } + remove(fromPos, toPos) { + if (fromPos === void 0) { + fromPos = 0; + } + if (toPos === void 0) { + toPos = this.displayValue.length; + } + const removeDetails = super.remove(fromPos, toPos); + this._forEachBlocksInRange(fromPos, toPos, (b, _, bFromPos, bToPos) => { + removeDetails.aggregate(b.remove(bFromPos, bToPos)); + }); + return removeDetails; + } + nearestInputPos(cursorPos, direction) { + if (direction === void 0) { + direction = DIRECTION.NONE; + } + if (!this._blocks.length) return 0; + const cursor = new PatternCursor(this, cursorPos); + if (direction === DIRECTION.NONE) { + // ------------------------------------------------- + // NONE should only go out from fixed to the right! + // ------------------------------------------------- + if (cursor.pushRightBeforeInput()) return cursor.pos; + cursor.popState(); + if (cursor.pushLeftBeforeInput()) return cursor.pos; + return this.displayValue.length; + } + + // FORCE is only about a|* otherwise is 0 + if (direction === DIRECTION.LEFT || direction === DIRECTION.FORCE_LEFT) { + // try to break fast when *|a + if (direction === DIRECTION.LEFT) { + cursor.pushRightBeforeFilled(); + if (cursor.ok && cursor.pos === cursorPos) return cursorPos; + cursor.popState(); + } + + // forward flow + cursor.pushLeftBeforeInput(); + cursor.pushLeftBeforeRequired(); + cursor.pushLeftBeforeFilled(); + + // backward flow + if (direction === DIRECTION.LEFT) { + cursor.pushRightBeforeInput(); + cursor.pushRightBeforeRequired(); + if (cursor.ok && cursor.pos <= cursorPos) return cursor.pos; + cursor.popState(); + if (cursor.ok && cursor.pos <= cursorPos) return cursor.pos; + cursor.popState(); + } + if (cursor.ok) return cursor.pos; + if (direction === DIRECTION.FORCE_LEFT) return 0; + cursor.popState(); + if (cursor.ok) return cursor.pos; + cursor.popState(); + if (cursor.ok) return cursor.pos; + return 0; + } + if (direction === DIRECTION.RIGHT || direction === DIRECTION.FORCE_RIGHT) { + // forward flow + cursor.pushRightBeforeInput(); + cursor.pushRightBeforeRequired(); + if (cursor.pushRightBeforeFilled()) return cursor.pos; + if (direction === DIRECTION.FORCE_RIGHT) return this.displayValue.length; + + // backward flow + cursor.popState(); + if (cursor.ok) return cursor.pos; + cursor.popState(); + if (cursor.ok) return cursor.pos; + return this.nearestInputPos(cursorPos, DIRECTION.LEFT); + } + return cursorPos; + } + totalInputPositions(fromPos, toPos) { + if (fromPos === void 0) { + fromPos = 0; + } + if (toPos === void 0) { + toPos = this.displayValue.length; + } + let total = 0; + this._forEachBlocksInRange(fromPos, toPos, (b, _, bFromPos, bToPos) => { + total += b.totalInputPositions(bFromPos, bToPos); + }); + return total; + } + + /** Get block by name */ + maskedBlock(name) { + return this.maskedBlocks(name)[0]; + } + + /** Get all blocks by name */ + maskedBlocks(name) { + const indices = this._maskedBlocks[name]; + if (!indices) return []; + return indices.map(gi => this._blocks[gi]); + } + } + MaskedPattern.DEFAULTS = { + lazy: true, + placeholderChar: '_' + }; + MaskedPattern.STOP_CHAR = '`'; + MaskedPattern.ESCAPE_CHAR = '\\'; + MaskedPattern.InputDefinition = PatternInputDefinition; + MaskedPattern.FixedDefinition = PatternFixedDefinition; + IMask.MaskedPattern = MaskedPattern; + + /** Pattern which accepts ranges */ + class MaskedRange extends MaskedPattern { + /** + Optionally sets max length of pattern. + Used when pattern length is longer then `to` param length. Pads zeros at start in this case. + */ + + /** Min bound */ + + /** Max bound */ + + /** */ + + get _matchFrom() { + return this.maxLength - String(this.from).length; + } + constructor(opts) { + super(opts); // mask will be created in _update + } + + updateOptions(opts) { + super.updateOptions(opts); + } + _update(opts) { + const { + to = this.to || 0, + from = this.from || 0, + maxLength = this.maxLength || 0, + autofix = this.autofix, + ...patternOpts + } = opts; + this.to = to; + this.from = from; + this.maxLength = Math.max(String(to).length, maxLength); + this.autofix = autofix; + const fromStr = String(this.from).padStart(this.maxLength, '0'); + const toStr = String(this.to).padStart(this.maxLength, '0'); + let sameCharsCount = 0; + while (sameCharsCount < toStr.length && toStr[sameCharsCount] === fromStr[sameCharsCount]) ++sameCharsCount; + patternOpts.mask = toStr.slice(0, sameCharsCount).replace(/0/g, '\\0') + '0'.repeat(this.maxLength - sameCharsCount); + super._update(patternOpts); + } + get isComplete() { + return super.isComplete && Boolean(this.value); + } + boundaries(str) { + let minstr = ''; + let maxstr = ''; + const [, placeholder, num] = str.match(/^(\D*)(\d*)(\D*)/) || []; + if (num) { + minstr = '0'.repeat(placeholder.length) + num; + maxstr = '9'.repeat(placeholder.length) + num; + } + minstr = minstr.padEnd(this.maxLength, '0'); + maxstr = maxstr.padEnd(this.maxLength, '9'); + return [minstr, maxstr]; + } + doPrepareChar(ch, flags) { + if (flags === void 0) { + flags = {}; + } + let details; + [ch, details] = super.doPrepareChar(ch.replace(/\D/g, ''), flags); + if (!this.autofix || !ch) return [ch, details]; + const fromStr = String(this.from).padStart(this.maxLength, '0'); + const toStr = String(this.to).padStart(this.maxLength, '0'); + const nextVal = this.value + ch; + if (nextVal.length > this.maxLength) return ['', details]; + const [minstr, maxstr] = this.boundaries(nextVal); + if (Number(maxstr) < this.from) return [fromStr[nextVal.length - 1], details]; + if (Number(minstr) > this.to) { + if (this.autofix === 'pad' && nextVal.length < this.maxLength) { + return ['', details.aggregate(this.append(fromStr[nextVal.length - 1] + ch, flags))]; + } + return [toStr[nextVal.length - 1], details]; + } + return [ch, details]; + } + doValidate(flags) { + const str = this.value; + const firstNonZero = str.search(/[^0]/); + if (firstNonZero === -1 && str.length <= this._matchFrom) return true; + const [minstr, maxstr] = this.boundaries(str); + return this.from <= Number(maxstr) && Number(minstr) <= this.to && super.doValidate(flags); + } + } + IMask.MaskedRange = MaskedRange; + + /** Date mask */ + class MaskedDate extends MaskedPattern { + /** Pattern mask for date according to {@link MaskedDate#format} */ + + /** Start date */ + + /** End date */ + + /** */ + + /** Format typed value to string */ + + /** Parse string to get typed value */ + + constructor(opts) { + const { + mask, + pattern, + ...patternOpts + } = { + ...MaskedDate.DEFAULTS, + ...opts + }; + super({ + ...patternOpts, + mask: isString(mask) ? mask : pattern + }); + } + updateOptions(opts) { + super.updateOptions(opts); + } + _update(opts) { + const { + mask, + pattern, + blocks, + ...patternOpts + } = { + ...MaskedDate.DEFAULTS, + ...opts + }; + const patternBlocks = Object.assign({}, MaskedDate.GET_DEFAULT_BLOCKS()); + // adjust year block + if (opts.min) patternBlocks.Y.from = opts.min.getFullYear(); + if (opts.max) patternBlocks.Y.to = opts.max.getFullYear(); + if (opts.min && opts.max && patternBlocks.Y.from === patternBlocks.Y.to) { + patternBlocks.m.from = opts.min.getMonth() + 1; + patternBlocks.m.to = opts.max.getMonth() + 1; + if (patternBlocks.m.from === patternBlocks.m.to) { + patternBlocks.d.from = opts.min.getDate(); + patternBlocks.d.to = opts.max.getDate(); + } + } + Object.assign(patternBlocks, this.blocks, blocks); + + // add autofix + Object.keys(patternBlocks).forEach(bk => { + const b = patternBlocks[bk]; + if (!('autofix' in b) && 'autofix' in opts) b.autofix = opts.autofix; + }); + super._update({ + ...patternOpts, + mask: isString(mask) ? mask : pattern, + blocks: patternBlocks + }); + } + doValidate(flags) { + const date = this.date; + return super.doValidate(flags) && (!this.isComplete || this.isDateExist(this.value) && date != null && (this.min == null || this.min <= date) && (this.max == null || date <= this.max)); + } + + /** Checks if date is exists */ + isDateExist(str) { + return this.format(this.parse(str, this), this).indexOf(str) >= 0; + } + + /** Parsed Date */ + get date() { + return this.typedValue; + } + set date(date) { + this.typedValue = date; + } + get typedValue() { + return this.isComplete ? super.typedValue : null; + } + set typedValue(value) { + super.typedValue = value; + } + maskEquals(mask) { + return mask === Date || super.maskEquals(mask); + } + } + MaskedDate.GET_DEFAULT_BLOCKS = () => ({ + d: { + mask: MaskedRange, + from: 1, + to: 31, + maxLength: 2 + }, + m: { + mask: MaskedRange, + from: 1, + to: 12, + maxLength: 2 + }, + Y: { + mask: MaskedRange, + from: 1900, + to: 9999 + } + }); + MaskedDate.DEFAULTS = { + mask: Date, + pattern: 'd{.}`m{.}`Y', + format: (date, masked) => { + if (!date) return ''; + const day = String(date.getDate()).padStart(2, '0'); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const year = date.getFullYear(); + return [day, month, year].join('.'); + }, + parse: (str, masked) => { + const [day, month, year] = str.split('.').map(Number); + return new Date(year, month - 1, day); + } + }; + IMask.MaskedDate = MaskedDate; + + /** Dynamic mask for choosing appropriate mask in run-time */ + class MaskedDynamic extends Masked { + /** Currently chosen mask */ + + /** Currently chosen mask */ + + /** Compliled {@link Masked} options */ + + /** Chooses {@link Masked} depending on input value */ + + constructor(opts) { + super({ + ...MaskedDynamic.DEFAULTS, + ...opts + }); + this.currentMask = undefined; + } + updateOptions(opts) { + super.updateOptions(opts); + } + _update(opts) { + super._update(opts); + if ('mask' in opts) { + this.exposeMask = undefined; + // mask could be totally dynamic with only `dispatch` option + this.compiledMasks = Array.isArray(opts.mask) ? opts.mask.map(m => { + const { + expose, + ...maskOpts + } = normalizeOpts(m); + const masked = createMask({ + overwrite: this._overwrite, + eager: this._eager, + skipInvalid: this._skipInvalid, + ...maskOpts + }); + if (expose) this.exposeMask = masked; + return masked; + }) : []; + + // this.currentMask = this.doDispatch(''); // probably not needed but lets see + } + } + + _appendCharRaw(ch, flags) { + if (flags === void 0) { + flags = {}; + } + const details = this._applyDispatch(ch, flags); + if (this.currentMask) { + details.aggregate(this.currentMask._appendChar(ch, this.currentMaskFlags(flags))); + } + return details; + } + _applyDispatch(appended, flags, tail) { + if (appended === void 0) { + appended = ''; + } + if (flags === void 0) { + flags = {}; + } + if (tail === void 0) { + tail = ''; + } + const prevValueBeforeTail = flags.tail && flags._beforeTailState != null ? flags._beforeTailState._value : this.value; + const inputValue = this.rawInputValue; + const insertValue = flags.tail && flags._beforeTailState != null ? flags._beforeTailState._rawInputValue : inputValue; + const tailValue = inputValue.slice(insertValue.length); + const prevMask = this.currentMask; + const details = new ChangeDetails(); + const prevMaskState = prevMask == null ? void 0 : prevMask.state; + + // clone flags to prevent overwriting `_beforeTailState` + this.currentMask = this.doDispatch(appended, { + ...flags + }, tail); + + // restore state after dispatch + if (this.currentMask) { + if (this.currentMask !== prevMask) { + // if mask changed reapply input + this.currentMask.reset(); + if (insertValue) { + const d = this.currentMask.append(insertValue, { + raw: true + }); + details.tailShift = d.inserted.length - prevValueBeforeTail.length; + } + if (tailValue) { + details.tailShift += this.currentMask.append(tailValue, { + raw: true, + tail: true + }).tailShift; + } + } else if (prevMaskState) { + // Dispatch can do something bad with state, so + // restore prev mask state + this.currentMask.state = prevMaskState; + } + } + return details; + } + _appendPlaceholder() { + const details = this._applyDispatch(); + if (this.currentMask) { + details.aggregate(this.currentMask._appendPlaceholder()); + } + return details; + } + _appendEager() { + const details = this._applyDispatch(); + if (this.currentMask) { + details.aggregate(this.currentMask._appendEager()); + } + return details; + } + appendTail(tail) { + const details = new ChangeDetails(); + if (tail) details.aggregate(this._applyDispatch('', {}, tail)); + return details.aggregate(this.currentMask ? this.currentMask.appendTail(tail) : super.appendTail(tail)); + } + currentMaskFlags(flags) { + var _flags$_beforeTailSta, _flags$_beforeTailSta2; + return { + ...flags, + _beforeTailState: ((_flags$_beforeTailSta = flags._beforeTailState) == null ? void 0 : _flags$_beforeTailSta.currentMaskRef) === this.currentMask && ((_flags$_beforeTailSta2 = flags._beforeTailState) == null ? void 0 : _flags$_beforeTailSta2.currentMask) || flags._beforeTailState + }; + } + doDispatch(appended, flags, tail) { + if (flags === void 0) { + flags = {}; + } + if (tail === void 0) { + tail = ''; + } + return this.dispatch(appended, this, flags, tail); + } + doValidate(flags) { + return super.doValidate(flags) && (!this.currentMask || this.currentMask.doValidate(this.currentMaskFlags(flags))); + } + doPrepare(str, flags) { + if (flags === void 0) { + flags = {}; + } + let [s, details] = super.doPrepare(str, flags); + if (this.currentMask) { + let currentDetails; + [s, currentDetails] = super.doPrepare(s, this.currentMaskFlags(flags)); + details = details.aggregate(currentDetails); + } + return [s, details]; + } + doPrepareChar(str, flags) { + if (flags === void 0) { + flags = {}; + } + let [s, details] = super.doPrepareChar(str, flags); + if (this.currentMask) { + let currentDetails; + [s, currentDetails] = super.doPrepareChar(s, this.currentMaskFlags(flags)); + details = details.aggregate(currentDetails); + } + return [s, details]; + } + reset() { + var _this$currentMask; + (_this$currentMask = this.currentMask) == null ? void 0 : _this$currentMask.reset(); + this.compiledMasks.forEach(m => m.reset()); + } + get value() { + return this.exposeMask ? this.exposeMask.value : this.currentMask ? this.currentMask.value : ''; + } + set value(value) { + if (this.exposeMask) { + this.exposeMask.value = value; + this.currentMask = this.exposeMask; + this._applyDispatch(); + } else super.value = value; + } + get unmaskedValue() { + return this.exposeMask ? this.exposeMask.unmaskedValue : this.currentMask ? this.currentMask.unmaskedValue : ''; + } + set unmaskedValue(unmaskedValue) { + if (this.exposeMask) { + this.exposeMask.unmaskedValue = unmaskedValue; + this.currentMask = this.exposeMask; + this._applyDispatch(); + } else super.unmaskedValue = unmaskedValue; + } + get typedValue() { + return this.exposeMask ? this.exposeMask.typedValue : this.currentMask ? this.currentMask.typedValue : ''; + } + set typedValue(typedValue) { + if (this.exposeMask) { + this.exposeMask.typedValue = typedValue; + this.currentMask = this.exposeMask; + this._applyDispatch(); + return; + } + let unmaskedValue = String(typedValue); + + // double check it + if (this.currentMask) { + this.currentMask.typedValue = typedValue; + unmaskedValue = this.currentMask.unmaskedValue; + } + this.unmaskedValue = unmaskedValue; + } + get displayValue() { + return this.currentMask ? this.currentMask.displayValue : ''; + } + get isComplete() { + var _this$currentMask2; + return Boolean((_this$currentMask2 = this.currentMask) == null ? void 0 : _this$currentMask2.isComplete); + } + get isFilled() { + var _this$currentMask3; + return Boolean((_this$currentMask3 = this.currentMask) == null ? void 0 : _this$currentMask3.isFilled); + } + remove(fromPos, toPos) { + const details = new ChangeDetails(); + if (this.currentMask) { + details.aggregate(this.currentMask.remove(fromPos, toPos)) + // update with dispatch + .aggregate(this._applyDispatch()); + } + return details; + } + get state() { + var _this$currentMask4; + return { + ...super.state, + _rawInputValue: this.rawInputValue, + compiledMasks: this.compiledMasks.map(m => m.state), + currentMaskRef: this.currentMask, + currentMask: (_this$currentMask4 = this.currentMask) == null ? void 0 : _this$currentMask4.state + }; + } + set state(state) { + const { + compiledMasks, + currentMaskRef, + currentMask, + ...maskedState + } = state; + if (compiledMasks) this.compiledMasks.forEach((m, mi) => m.state = compiledMasks[mi]); + if (currentMaskRef != null) { + this.currentMask = currentMaskRef; + this.currentMask.state = currentMask; + } + super.state = maskedState; + } + extractInput(fromPos, toPos, flags) { + return this.currentMask ? this.currentMask.extractInput(fromPos, toPos, flags) : ''; + } + extractTail(fromPos, toPos) { + return this.currentMask ? this.currentMask.extractTail(fromPos, toPos) : super.extractTail(fromPos, toPos); + } + doCommit() { + if (this.currentMask) this.currentMask.doCommit(); + super.doCommit(); + } + nearestInputPos(cursorPos, direction) { + return this.currentMask ? this.currentMask.nearestInputPos(cursorPos, direction) : super.nearestInputPos(cursorPos, direction); + } + get overwrite() { + return this.currentMask ? this.currentMask.overwrite : this._overwrite; + } + set overwrite(overwrite) { + this._overwrite = overwrite; + } + get eager() { + return this.currentMask ? this.currentMask.eager : this._eager; + } + set eager(eager) { + this._eager = eager; + } + get skipInvalid() { + return this.currentMask ? this.currentMask.skipInvalid : this._skipInvalid; + } + set skipInvalid(skipInvalid) { + this._skipInvalid = skipInvalid; + } + maskEquals(mask) { + return Array.isArray(mask) ? this.compiledMasks.every((m, mi) => { + if (!mask[mi]) return; + const { + mask: oldMask, + ...restOpts + } = mask[mi]; + return objectIncludes(m, restOpts) && m.maskEquals(oldMask); + }) : super.maskEquals(mask); + } + typedValueEquals(value) { + var _this$currentMask5; + return Boolean((_this$currentMask5 = this.currentMask) == null ? void 0 : _this$currentMask5.typedValueEquals(value)); + } + } + MaskedDynamic.DEFAULTS = void 0; + MaskedDynamic.DEFAULTS = { + dispatch: (appended, masked, flags, tail) => { + if (!masked.compiledMasks.length) return; + const inputValue = masked.rawInputValue; + + // simulate input + const inputs = masked.compiledMasks.map((m, index) => { + const isCurrent = masked.currentMask === m; + const startInputPos = isCurrent ? m.displayValue.length : m.nearestInputPos(m.displayValue.length, DIRECTION.FORCE_LEFT); + if (m.rawInputValue !== inputValue) { + m.reset(); + m.append(inputValue, { + raw: true + }); + } else if (!isCurrent) { + m.remove(startInputPos); + } + m.append(appended, masked.currentMaskFlags(flags)); + m.appendTail(tail); + return { + index, + weight: m.rawInputValue.length, + totalInputPositions: m.totalInputPositions(0, Math.max(startInputPos, m.nearestInputPos(m.displayValue.length, DIRECTION.FORCE_LEFT))) + }; + }); + + // pop masks with longer values first + inputs.sort((i1, i2) => i2.weight - i1.weight || i2.totalInputPositions - i1.totalInputPositions); + return masked.compiledMasks[inputs[0].index]; + } + }; + IMask.MaskedDynamic = MaskedDynamic; + + /** Pattern which validates enum values */ + class MaskedEnum extends MaskedPattern { + updateOptions(opts) { + super.updateOptions(opts); + } + _update(opts) { + const { + enum: _enum, + ...eopts + } = opts; + if (_enum) { + eopts.mask = '*'.repeat(_enum[0].length); + this.enum = _enum; + } + super._update(eopts); + } + doValidate(flags) { + return this.enum.some(e => e.indexOf(this.unmaskedValue) >= 0) && super.doValidate(flags); + } + } + IMask.MaskedEnum = MaskedEnum; + + /** Masking by custom Function */ + class MaskedFunction extends Masked { + /** */ + + /** Enable characters overwriting */ + + /** */ + + /** */ + + updateOptions(opts) { + super.updateOptions(opts); + } + _update(opts) { + super._update({ + ...opts, + validate: opts.mask + }); + } + } + IMask.MaskedFunction = MaskedFunction; + + /** + Number mask + */ + class MaskedNumber extends Masked { + /** Single char */ + + /** Single char */ + + /** Array of single chars */ + + /** */ + + /** */ + + /** Digits after point */ + + /** Flag to remove leading and trailing zeros in the end of editing */ + + /** Flag to pad trailing zeros after point in the end of editing */ + + /** Enable characters overwriting */ + + /** */ + + /** */ + + /** Format typed value to string */ + + /** Parse string to get typed value */ + + constructor(opts) { + super({ + ...MaskedNumber.DEFAULTS, + ...opts + }); + } + updateOptions(opts) { + super.updateOptions(opts); + } + _update(opts) { + super._update(opts); + this._updateRegExps(); + } + _updateRegExps() { + const start = '^' + (this.allowNegative ? '[+|\\-]?' : ''); + const mid = '\\d*'; + const end = (this.scale ? "(" + escapeRegExp(this.radix) + "\\d{0," + this.scale + "})?" : '') + '$'; + this._numberRegExp = new RegExp(start + mid + end); + this._mapToRadixRegExp = new RegExp("[" + this.mapToRadix.map(escapeRegExp).join('') + "]", 'g'); + this._thousandsSeparatorRegExp = new RegExp(escapeRegExp(this.thousandsSeparator), 'g'); + } + _removeThousandsSeparators(value) { + return value.replace(this._thousandsSeparatorRegExp, ''); + } + _insertThousandsSeparators(value) { + // https://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript + const parts = value.split(this.radix); + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandsSeparator); + return parts.join(this.radix); + } + doPrepareChar(ch, flags) { + if (flags === void 0) { + flags = {}; + } + ch = this._removeThousandsSeparators(this.scale && this.mapToRadix.length && ( + /* + radix should be mapped when + 1) input is done from keyboard = flags.input && flags.raw + 2) unmasked value is set = !flags.input && !flags.raw + and should not be mapped when + 1) value is set = flags.input && !flags.raw + 2) raw value is set = !flags.input && flags.raw + */ + flags.input && flags.raw || !flags.input && !flags.raw) ? ch.replace(this._mapToRadixRegExp, this.radix) : ch); + const [prepCh, details] = super.doPrepareChar(ch, flags); + if (ch && !prepCh) details.skip = true; + if (prepCh && !this.allowPositive && !this.value && prepCh !== '-') details.aggregate(this._appendChar('-')); + return [prepCh, details]; + } + _separatorsCount(to, extendOnSeparators) { + if (extendOnSeparators === void 0) { + extendOnSeparators = false; + } + let count = 0; + for (let pos = 0; pos < to; ++pos) { + if (this._value.indexOf(this.thousandsSeparator, pos) === pos) { + ++count; + if (extendOnSeparators) to += this.thousandsSeparator.length; + } + } + return count; + } + _separatorsCountFromSlice(slice) { + if (slice === void 0) { + slice = this._value; + } + return this._separatorsCount(this._removeThousandsSeparators(slice).length, true); + } + extractInput(fromPos, toPos, flags) { + if (fromPos === void 0) { + fromPos = 0; + } + if (toPos === void 0) { + toPos = this.displayValue.length; + } + [fromPos, toPos] = this._adjustRangeWithSeparators(fromPos, toPos); + return this._removeThousandsSeparators(super.extractInput(fromPos, toPos, flags)); + } + _appendCharRaw(ch, flags) { + if (flags === void 0) { + flags = {}; + } + if (!this.thousandsSeparator) return super._appendCharRaw(ch, flags); + const prevBeforeTailValue = flags.tail && flags._beforeTailState ? flags._beforeTailState._value : this._value; + const prevBeforeTailSeparatorsCount = this._separatorsCountFromSlice(prevBeforeTailValue); + this._value = this._removeThousandsSeparators(this.value); + const appendDetails = super._appendCharRaw(ch, flags); + this._value = this._insertThousandsSeparators(this._value); + const beforeTailValue = flags.tail && flags._beforeTailState ? flags._beforeTailState._value : this._value; + const beforeTailSeparatorsCount = this._separatorsCountFromSlice(beforeTailValue); + appendDetails.tailShift += (beforeTailSeparatorsCount - prevBeforeTailSeparatorsCount) * this.thousandsSeparator.length; + appendDetails.skip = !appendDetails.rawInserted && ch === this.thousandsSeparator; + return appendDetails; + } + _findSeparatorAround(pos) { + if (this.thousandsSeparator) { + const searchFrom = pos - this.thousandsSeparator.length + 1; + const separatorPos = this.value.indexOf(this.thousandsSeparator, searchFrom); + if (separatorPos <= pos) return separatorPos; + } + return -1; + } + _adjustRangeWithSeparators(from, to) { + const separatorAroundFromPos = this._findSeparatorAround(from); + if (separatorAroundFromPos >= 0) from = separatorAroundFromPos; + const separatorAroundToPos = this._findSeparatorAround(to); + if (separatorAroundToPos >= 0) to = separatorAroundToPos + this.thousandsSeparator.length; + return [from, to]; + } + remove(fromPos, toPos) { + if (fromPos === void 0) { + fromPos = 0; + } + if (toPos === void 0) { + toPos = this.displayValue.length; + } + [fromPos, toPos] = this._adjustRangeWithSeparators(fromPos, toPos); + const valueBeforePos = this.value.slice(0, fromPos); + const valueAfterPos = this.value.slice(toPos); + const prevBeforeTailSeparatorsCount = this._separatorsCount(valueBeforePos.length); + this._value = this._insertThousandsSeparators(this._removeThousandsSeparators(valueBeforePos + valueAfterPos)); + const beforeTailSeparatorsCount = this._separatorsCountFromSlice(valueBeforePos); + return new ChangeDetails({ + tailShift: (beforeTailSeparatorsCount - prevBeforeTailSeparatorsCount) * this.thousandsSeparator.length + }); + } + nearestInputPos(cursorPos, direction) { + if (!this.thousandsSeparator) return cursorPos; + switch (direction) { + case DIRECTION.NONE: + case DIRECTION.LEFT: + case DIRECTION.FORCE_LEFT: + { + const separatorAtLeftPos = this._findSeparatorAround(cursorPos - 1); + if (separatorAtLeftPos >= 0) { + const separatorAtLeftEndPos = separatorAtLeftPos + this.thousandsSeparator.length; + if (cursorPos < separatorAtLeftEndPos || this.value.length <= separatorAtLeftEndPos || direction === DIRECTION.FORCE_LEFT) { + return separatorAtLeftPos; + } + } + break; + } + case DIRECTION.RIGHT: + case DIRECTION.FORCE_RIGHT: + { + const separatorAtRightPos = this._findSeparatorAround(cursorPos); + if (separatorAtRightPos >= 0) { + return separatorAtRightPos + this.thousandsSeparator.length; + } + } + } + return cursorPos; + } + doValidate(flags) { + // validate as string + let valid = Boolean(this._removeThousandsSeparators(this.value).match(this._numberRegExp)); + if (valid) { + // validate as number + const number = this.number; + valid = valid && !isNaN(number) && ( + // check min bound for negative values + this.min == null || this.min >= 0 || this.min <= this.number) && ( + // check max bound for positive values + this.max == null || this.max <= 0 || this.number <= this.max); + } + return valid && super.doValidate(flags); + } + doCommit() { + if (this.value) { + const number = this.number; + let validnum = number; + + // check bounds + if (this.min != null) validnum = Math.max(validnum, this.min); + if (this.max != null) validnum = Math.min(validnum, this.max); + if (validnum !== number) this.unmaskedValue = this.format(validnum, this); + let formatted = this.value; + if (this.normalizeZeros) formatted = this._normalizeZeros(formatted); + if (this.padFractionalZeros && this.scale > 0) formatted = this._padFractionalZeros(formatted); + this._value = formatted; + } + super.doCommit(); + } + _normalizeZeros(value) { + const parts = this._removeThousandsSeparators(value).split(this.radix); + + // remove leading zeros + parts[0] = parts[0].replace(/^(\D*)(0*)(\d*)/, (match, sign, zeros, num) => sign + num); + // add leading zero + if (value.length && !/\d$/.test(parts[0])) parts[0] = parts[0] + '0'; + if (parts.length > 1) { + parts[1] = parts[1].replace(/0*$/, ''); // remove trailing zeros + if (!parts[1].length) parts.length = 1; // remove fractional + } + + return this._insertThousandsSeparators(parts.join(this.radix)); + } + _padFractionalZeros(value) { + if (!value) return value; + const parts = value.split(this.radix); + if (parts.length < 2) parts.push(''); + parts[1] = parts[1].padEnd(this.scale, '0'); + return parts.join(this.radix); + } + doSkipInvalid(ch, flags, checkTail) { + if (flags === void 0) { + flags = {}; + } + const dropFractional = this.scale === 0 && ch !== this.thousandsSeparator && (ch === this.radix || ch === MaskedNumber.UNMASKED_RADIX || this.mapToRadix.includes(ch)); + return super.doSkipInvalid(ch, flags, checkTail) && !dropFractional; + } + get unmaskedValue() { + return this._removeThousandsSeparators(this._normalizeZeros(this.value)).replace(this.radix, MaskedNumber.UNMASKED_RADIX); + } + set unmaskedValue(unmaskedValue) { + super.unmaskedValue = unmaskedValue; + } + get typedValue() { + return this.parse(this.unmaskedValue, this); + } + set typedValue(n) { + this.rawInputValue = this.format(n, this).replace(MaskedNumber.UNMASKED_RADIX, this.radix); + } + + /** Parsed Number */ + get number() { + return this.typedValue; + } + set number(number) { + this.typedValue = number; + } + + /** + Is negative allowed + */ + get allowNegative() { + return this.min != null && this.min < 0 || this.max != null && this.max < 0; + } + + /** + Is positive allowed + */ + get allowPositive() { + return this.min != null && this.min > 0 || this.max != null && this.max > 0; + } + typedValueEquals(value) { + // handle 0 -> '' case (typed = 0 even if value = '') + // for details see https://github.com/uNmAnNeR/imaskjs/issues/134 + return (super.typedValueEquals(value) || MaskedNumber.EMPTY_VALUES.includes(value) && MaskedNumber.EMPTY_VALUES.includes(this.typedValue)) && !(value === 0 && this.value === ''); + } + } + MaskedNumber.UNMASKED_RADIX = '.'; + MaskedNumber.EMPTY_VALUES = [...Masked.EMPTY_VALUES, 0]; + MaskedNumber.DEFAULTS = { + mask: Number, + radix: ',', + thousandsSeparator: '', + mapToRadix: [MaskedNumber.UNMASKED_RADIX], + min: Number.MIN_SAFE_INTEGER, + max: Number.MAX_SAFE_INTEGER, + scale: 2, + normalizeZeros: true, + padFractionalZeros: false, + parse: Number, + format: n => n.toLocaleString('en-US', { + useGrouping: false, + maximumFractionDigits: 20 + }) + }; + IMask.MaskedNumber = MaskedNumber; + + /** Mask pipe source and destination types */ + const PIPE_TYPE = { + MASKED: 'value', + UNMASKED: 'unmaskedValue', + TYPED: 'typedValue' + }; + /** Creates new pipe function depending on mask type, source and destination options */ + function createPipe(arg, from, to) { + if (from === void 0) { + from = PIPE_TYPE.MASKED; + } + if (to === void 0) { + to = PIPE_TYPE.MASKED; + } + const masked = createMask(arg); + return value => masked.runIsolated(m => { + m[from] = value; + return m[to]; + }); + } + + /** Pipes value through mask depending on mask type, source and destination options */ + function pipe(value, mask, from, to) { + return createPipe(mask, from, to)(value); + } + IMask.PIPE_TYPE = PIPE_TYPE; + IMask.createPipe = createPipe; + IMask.pipe = pipe; + + try { + globalThis.IMask = IMask; + } catch {} + + exports.ChangeDetails = ChangeDetails; + exports.ChunksTailDetails = ChunksTailDetails; + exports.DIRECTION = DIRECTION; + exports.HTMLContenteditableMaskElement = HTMLContenteditableMaskElement; + exports.HTMLInputMaskElement = HTMLInputMaskElement; + exports.HTMLMaskElement = HTMLMaskElement; + exports.InputMask = InputMask; + exports.MaskElement = MaskElement; + exports.Masked = Masked; + exports.MaskedDate = MaskedDate; + exports.MaskedDynamic = MaskedDynamic; + exports.MaskedEnum = MaskedEnum; + exports.MaskedFunction = MaskedFunction; + exports.MaskedNumber = MaskedNumber; + exports.MaskedPattern = MaskedPattern; + exports.MaskedRange = MaskedRange; + exports.MaskedRegExp = MaskedRegExp; + exports.PIPE_TYPE = PIPE_TYPE; + exports.PatternFixedDefinition = PatternFixedDefinition; + exports.PatternInputDefinition = PatternInputDefinition; + exports.createMask = createMask; + exports.createPipe = createPipe; + exports.default = IMask; + exports.forceDirection = forceDirection; + exports.normalizeOpts = normalizeOpts; + exports.pipe = pipe; + + Object.defineProperty(exports, '__esModule', { value: true }); + +})); +//# sourceMappingURL=imask.js.map + diff --git a/mirzaev/ebala/system/public/js/loader.js b/mirzaev/ebala/system/public/js/loader.js new file mode 100755 index 0000000..6e4d7ca --- /dev/null +++ b/mirzaev/ebala/system/public/js/loader.js @@ -0,0 +1,183 @@ +"use strict"; + +if (typeof window.loader !== "function") { + // Not initialized + + // Initialize of the class in global namespace + window.loader = class pages { + /** + * Storage for history + */ + static storage = {}; + + /** + * Element: menu + * + * @return {void} + */ + static async menu() { + return await fetch("/elements/menu", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }) + .then((response) => response.text()) + .then((data) => { + // Deleting outdated elements + for ( + const element of document.querySelectorAll( + `section[id="menu"]`, + ) + ) element.remove(); + + const section = document.createElement("section"); + document.body.prepend(section); + section.outerHTML = data; + }); + } + + /** + * Page: tasks + * + * @return {void} + */ + static async index() { + // Запрос к серверу + return await fetch("/", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }) + .then((response) => response.text()) + .then((data) => { + // Write path in history + history.pushState(this.storage, "/", "/"); + + // Write path to the current directory buffer + core.page = 'tasks'; + + // Write content in document + document.body.getElementsByTagName("main")[0].innerHTML = data; + }); + } + + /** + * Page: administrators + * + * @return {void} + */ + static async administrators() { + return await fetch("/administrators", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }) + .then((response) => response.text()) + .then((data) => { + document.body.getElementsByTagName("main")[0].innerHTML = data; + }); + } + + /** + * Page: operators + * + * @return {void} + */ + static async operators() { + return await fetch("/operators", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }) + .then((response) => response.text()) + .then((data) => { + document.body.getElementsByTagName("main")[0].innerHTML = data; + }); + } + /** + * Page: markets + * + * @return {void} + */ + static async markets() { + return await fetch("/markets", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }) + .then((response) => response.text()) + .then((data) => { + document.body.getElementsByTagName("main")[0].innerHTML = data; + }); + } + + /** + * Page: workers + * + * @return {void} + */ + static async workers() { + return await fetch("/workers", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }) + .then((response) => response.text()) + .then((data) => { + // Write path in history + history.pushState(this.storage, "/workers", "/workers"); + + // Write path to the current directory buffer + core.page = 'workers'; + + // Write content in document + document.body.getElementsByTagName("main")[0].innerHTML = data; + }); + } + + /** + * Page: settings + * + * @return {void} + */ + static async settings() { + return await fetch("/settings", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }) + .then((response) => response.text()) + .then((data) => { + document.body.getElementsByTagName("main")[0].innerHTML = data; + }); + } + + /** + * Page: account + * + * @return {void} + */ + static async account() { + // Initialization of the account identifier + account = Cookies.get(`account_id`) ?? "account"; + + return await fetch(`/${account}`, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }) + .then((response) => response.text()) + .then((data) => { + document.body.getElementsByTagName("main")[0].innerHTML = data; + }); + } + }; +} diff --git a/mirzaev/ebala/system/public/js/markets.js b/mirzaev/ebala/system/public/js/markets.js new file mode 100644 index 0000000..2ee658f --- /dev/null +++ b/mirzaev/ebala/system/public/js/markets.js @@ -0,0 +1,40 @@ +"use strict"; + +if (typeof window.markets !== "function") { + // Not initialized + + // Initialize of the class in global namespace + window.markets = class markets { + /** + * Сгенерировать список + * + * @return {array|null} Массив HTML-элементов +{% endfor %} diff --git a/mirzaev/ebala/system/views/lists/workers.html b/mirzaev/ebala/system/views/lists/workers.html new file mode 100644 index 0000000..9d3a4c1 --- /dev/null +++ b/mirzaev/ebala/system/views/lists/workers.html @@ -0,0 +1,3 @@ +{% for worker in workers %} + +{% endfor %} diff --git a/mirzaev/ebala/system/views/lists/works.html b/mirzaev/ebala/system/views/lists/works.html new file mode 100644 index 0000000..aa08bcf --- /dev/null +++ b/mirzaev/ebala/system/views/lists/works.html @@ -0,0 +1,16 @@ +{% if exist is same as(true) %} +{% for work in works %} + +{% endfor %} +{% else %} +{% if task is not null %} + + + +{% endif %} + + {% for work in works %} + + {% endfor %} + +{% endif %} diff --git a/mirzaev/ebala/system/views/menu.html b/mirzaev/ebala/system/views/menu.html new file mode 100755 index 0000000..0984b99 --- /dev/null +++ b/mirzaev/ebala/system/views/menu.html @@ -0,0 +1,43 @@ +{% block css %} +{% endblock %} + +{% block body %} + +{% endblock %} + +{% block js %} +{% endblock %} diff --git a/mirzaev/ebala/system/views/pages/entry/administrator.html b/mirzaev/ebala/system/views/pages/entry/administrator.html new file mode 100755 index 0000000..3c6e7ea --- /dev/null +++ b/mirzaev/ebala/system/views/pages/entry/administrator.html @@ -0,0 +1,316 @@ +{% block css %} + + + + + +{% endblock %} + +{% block body %} +
+
+

Идентификация

+
+
+ +
+ + +
+ +
+
+ + +{% endblock %} + +{% block js %} + + + +{% endblock %} diff --git a/mirzaev/ebala/system/views/pages/entry/market.html b/mirzaev/ebala/system/views/pages/entry/market.html new file mode 100755 index 0000000..cb0bcad --- /dev/null +++ b/mirzaev/ebala/system/views/pages/entry/market.html @@ -0,0 +1,317 @@ +{% block css %} + + + + + +{% endblock %} + +{% block body %} +
+
+

Идентификация

+
+
+ +
+ + +
+ +
+
+ + +{% endblock %} + +{% block js %} + + + +{% endblock %} diff --git a/mirzaev/ebala/system/views/pages/entry/operator.html b/mirzaev/ebala/system/views/pages/entry/operator.html new file mode 100755 index 0000000..9f8e9f8 --- /dev/null +++ b/mirzaev/ebala/system/views/pages/entry/operator.html @@ -0,0 +1,316 @@ +{% block css %} + + + + + +{% endblock %} + +{% block body %} +
+
+

Идентификация

+
+
+ +
+ + +
+ +
+
+ + +{% endblock %} + +{% block js %} + + + +{% endblock %} diff --git a/mirzaev/ebala/system/views/pages/entry/worker.html b/mirzaev/ebala/system/views/pages/entry/worker.html new file mode 100755 index 0000000..361a540 --- /dev/null +++ b/mirzaev/ebala/system/views/pages/entry/worker.html @@ -0,0 +1,311 @@ +{% block css %} + + + + + +{% endblock %} + +{% block body %} +
+
+

Идентификация

+
+
+ +
+ + +
+ +
+
+ + +{% endblock %} + +{% block js %} + + + +{% endblock %} diff --git a/mirzaev/ebala/system/views/pages/info.txt b/mirzaev/ebala/system/views/pages/info.txt new file mode 100644 index 0000000..94a58e3 --- /dev/null +++ b/mirzaev/ebala/system/views/pages/info.txt @@ -0,0 +1,3 @@ +oninput="this.nextElementSibling.innerText = this.value; this.nextElementSibling.style.setProperty('--left', (((this.value / 5) * 96) + 12) + 'px');" +вычисляется по формуле +(((value - minValue) / (valueMax - valueMin)) * ((totalInputWidth - thumbHalfWidth) - thumbHalfWidth)) + thumbHalfWidth; diff --git a/mirzaev/ebala/system/views/pages/markets.html b/mirzaev/ebala/system/views/pages/markets.html new file mode 100644 index 0000000..fba91e3 --- /dev/null +++ b/mirzaev/ebala/system/views/pages/markets.html @@ -0,0 +1,118 @@ +{% extends('index.html') %} + +{% block css %} + + + + + + + + + +{% endblock %} + +{% block body %} +
+ + + +
+ + ФИО + Дата + Номер + Паспорт + Адрес + ИНН + Реквизиты + Комментарий + Статус +
+
+ + +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/mirzaev/ebala/system/views/pages/tasks.html b/mirzaev/ebala/system/views/pages/tasks.html new file mode 100644 index 0000000..12f197c --- /dev/null +++ b/mirzaev/ebala/system/views/pages/tasks.html @@ -0,0 +1,137 @@ +{% extends('index.html') %} + +{% block css %} + + + + + + + + + +{% endblock %} + +{% block body %} +
+ + + +
+ Дата + + ФИО + Работа + + + + + Адрес + Тип + ИНН + Комментарий + Чат +
+
+ + +{% endblock %} + +{% block js %} + + + + +{% endblock %} diff --git a/mirzaev/ebala/system/views/pages/tasks.html.old b/mirzaev/ebala/system/views/pages/tasks.html.old new file mode 100644 index 0000000..3e9112b --- /dev/null +++ b/mirzaev/ebala/system/views/pages/tasks.html.old @@ -0,0 +1,140 @@ +{% block css %} + + + + + + + + + +{% endblock %} + +{% block body %} +
+ + + +
+ Дата + + ФИО + Работа + + + + + Адрес + Тип + ИНН + Комментарий + Чат +
+
+ + +{% endblock %} + +{% block js %} +{% if server.REQUEST_METHOD == 'POST' %} + + + + +{% endif %} + + + +{% endblock %} diff --git a/mirzaev/ebala/system/views/pages/workers.html b/mirzaev/ebala/system/views/pages/workers.html new file mode 100644 index 0000000..dbe5d93 --- /dev/null +++ b/mirzaev/ebala/system/views/pages/workers.html @@ -0,0 +1,118 @@ +{% extends('index.html') %} + +{% block css %} + + + + + + + + + +{% endblock %} + +{% block body %} +
+ + + +
+ + ФИО + Дата + Номер + Паспорт + Адрес + ИНН + Реквизиты + Комментарий + Статус +
+
+ + +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/mirzaev/ebala/system/views/templater.php b/mirzaev/ebala/system/views/templater.php new file mode 100755 index 0000000..1bbee33 --- /dev/null +++ b/mirzaev/ebala/system/views/templater.php @@ -0,0 +1,191 @@ + + */ +final class templater extends controller implements ArrayAccess +{ + /** + * Реестр глобальных переменных + */ + public array $variables = []; + + /** + * Инстанция окружения twig + */ + public twig $twig; + + /** + * Конструктор + * + * @return void + */ + public function __construct(?session &$session = null, ?account &$account = null) + { + // Инициализация шаблонизатора + $this->twig = new twig(new FilesystemLoader(VIEWS)); + + // Инициализация глобальных переменных + $this->twig->addGlobal('server', $_SERVER); + $this->twig->addGlobal('cookie', $_COOKIE); // @todo DELETE THIS + $this->twig->addGlobal('cookies', $_COOKIE); + if (!empty($session->status())) { + $this->twig->addGlobal('session', $session); + $this->twig->addGlobal('buffer', $session->buffer[$_SERVER['INTERFACE']] ?? null); + } + if (!empty($account->status())) $this->twig->addGlobal('account', $account); + + // Инициализация расширений + $this->twig->addExtension(new intl()); + } + + /** + * Отрисовка HTML-документа + * + * @param string $file Относительный директории представлений путь до файла представления + * @param ?array $variables Реестр переменных + * + * @return ?string HTML-документ + */ + public function render(string $file, ?array $variables = null): ?string + { + // Генерация представления + return $this->twig->render($file, $variables ?? $this->variables); + } + + /** + * Записать + * + * Записывает переменную в реестр глобальных переменных + * + * @param string $name Название + * @param mixed $value Содержимое + * + * @return void + */ + public function __set(string $name, mixed $value = null): void + { + $this->variables[$name] = $value; + } + + /** + * Прочитать + * + * Читает переменную из реестра глобальных переменных + * + * @param string $name Название + * + * @return mixed Данные переменной из реестра глобальных переменных + */ + public function __get(string $name): mixed + { + return $this->variables[$name]; + } + + /** + * Проверить инициализированность + * + * Проверяет инициализированность переменной в буфере переменных представления + * + * @param string $name Название + * + * @return bool Переменная инициализирована? + */ + public function __isset(string $name): bool + { + return isset($this->variables[$name]); + } + + /** + * Удалить + * + * Деинициализирует переменную в реестре глобальных переменных + * + * @param string $name Название + * + * @return void + */ + public function __unset(string $name): void + { + unset($this->variables[$name]); + } + + /** + * Записать + * + * Записывает переменную в реестр глобальных переменных + * + * @param mixed $offset Сдвиг, либо идентификатор + * @param mixed $value Содержимое + * + * @return void + */ + public function offsetSet(mixed $offset, mixed $value): void + { + $this->variables[$offset] = $value; + } + + /** + * Прочитать + * + * Читает переменную из реестра глобальных переменных + * + * @param mixed $offset Сдвиг, либо идентификатор + * + * @return mixed Данные переменной из реестра глобальных переменных + */ + public function offsetGet(mixed $offset): mixed + { + return $this->variables[$offset]; + } + + /** + * Проверить инициализированность + * + * Проверяет инициализированность переменной в реестре глобальных переменных + * + * @param mixed $offset Сдвиг, либо идентификатор + * + * @return bool Инициализирована переменная? + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->variables[$offset]); + } + + /** + * Удалить + * + * Деинициализирует переменную в реестре глобальных переменных + * + * @param mixed $offset Сдвиг, либо идентификатор + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->variables[$offset]); + } +}