diff --git a/.gitignore b/.gitignore index 867c67a..437ff01 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,8 @@ yarn-error.log ###> liip/imagine-bundle ### /public/media/cache/ ###< liip/imagine-bundle ### + +###> friendsofphp/php-cs-fixer ### +/.php-cs-fixer.php +/.php-cs-fixer.cache +###< friendsofphp/php-cs-fixer ### diff --git a/composer.json b/composer.json index 61468bb..ab73c1e 100644 --- a/composer.json +++ b/composer.json @@ -113,7 +113,8 @@ ], "post-update-cmd": [ "@auto-scripts" - ] + ], + "php-cs-fixer": "php-cs-fixer --rules=@Symfony" }, "conflict": { "symfony/symfony": "*" @@ -126,6 +127,7 @@ }, "require-dev": { "deployer/deployer": "^7.3", + "friendsofphp/php-cs-fixer": "^3.84", "marcocesarato/php-conventional-changelog": "^1.17", "phpunit/phpunit": "^9.5", "symfony/browser-kit": "6.4.*", diff --git a/composer.lock b/composer.lock index 51bb745..a4ac516 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6b642cc934ca08eb66d76d03fa59acf7", + "content-hash": "57ad3e9efdaa066d552366d2c5d6a6fa", "packages": [ { "name": "beberlei/doctrineextensions", @@ -10578,6 +10578,296 @@ } ], "packages-dev": [ + { + "name": "clue/ndjson-react", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\NDJson\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", + "keywords": [ + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" + ], + "support": { + "issues": "https://github.com/clue/reactphp-ndjson/issues", + "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-12-23T10:58:28+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-09-19T14:15:21+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, { "name": "deployer/deployer", "version": "v7.5.12", @@ -10632,6 +10922,219 @@ ], "time": "2025-02-19T16:45:27+00:00" }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "8520451a140d3f46ac33042715115e290cf5785f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", + "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2024-08-06T10:04:20+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.84.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "38dad0767bf2a9b516b976852200ae722fe984ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/38dad0767bf2a9b516b976852200ae722fe984ca", + "reference": "38dad0767bf2a9b516b976852200ae722fe984ca", + "shasum": "" + }, + "require": { + "clue/ndjson-react": "^1.0", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.5", + "ext-filter": "*", + "ext-hash": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.2", + "php": "^7.4 || ^8.0", + "react/child-process": "^0.6.6", + "react/event-loop": "^1.0", + "react/promise": "^2.11 || ^3.0", + "react/socket": "^1.0", + "react/stream": "^1.0", + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0", + "symfony/console": "^5.4.45 || ^6.4.13 || ^7.0", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.13 || ^7.0", + "symfony/filesystem": "^5.4.45 || ^6.4.13 || ^7.0", + "symfony/finder": "^5.4.45 || ^6.4.17 || ^7.0", + "symfony/options-resolver": "^5.4.45 || ^6.4.16 || ^7.0", + "symfony/polyfill-mbstring": "^1.32", + "symfony/polyfill-php80": "^1.32", + "symfony/polyfill-php81": "^1.32", + "symfony/process": "^5.4.47 || ^6.4.20 || ^7.2", + "symfony/stopwatch": "^5.4.45 || ^6.4.19 || ^7.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3.1 || ^2.6", + "infection/infection": "^0.29.14", + "justinrainbow/json-schema": "^5.3 || ^6.4", + "keradus/cli-executor": "^2.2", + "mikey179/vfsstream": "^1.6.12", + "php-coveralls/php-coveralls": "^2.8", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", + "phpunit/phpunit": "^9.6.23 || ^10.5.47 || ^11.5.25", + "symfony/polyfill-php84": "^1.32", + "symfony/var-dumper": "^5.4.48 || ^6.4.23 || ^7.3.1", + "symfony/yaml": "^5.4.45 || ^6.4.23 || ^7.3.1" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "exclude-from-classmap": [ + "src/Fixer/Internal/*" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.84.0" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2025-07-15T18:21:57+00:00" + }, { "name": "marcocesarato/php-conventional-changelog", "version": "1.17.2", @@ -11443,6 +11946,532 @@ ], "time": "2025-05-02T06:40:34+00:00" }, + { + "name": "react/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/child-process", + "version": "v0.6.6", + "source": { + "type": "git", + "url": "https://github.com/reactphp/child-process.git", + "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/1721e2b93d89b745664353b9cfc8f155ba8a6159", + "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/event-loop": "^1.2", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/socket": "^1.16", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\ChildProcess\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven library for executing child processes with ReactPHP.", + "keywords": [ + "event-driven", + "process", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/child-process/issues", + "source": "https://github.com/reactphp/child-process/tree/v0.6.6" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-01-01T16:37:48+00:00" + }, + { + "name": "react/dns", + "version": "v1.13.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.13.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-13T14:18:03+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-13T13:48:05+00:00" + }, + { + "name": "react/promise", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-05-24T10:39:05+00:00" + }, + { + "name": "react/socket", + "version": "v1.16.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.16.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-07-26T10:38:09+00:00" + }, + { + "name": "react/stream", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" + }, { "name": "sebastian/cli-parser", "version": "1.0.2", diff --git a/config/packages/workflow.yaml b/config/packages/workflow.yaml index 19caab6..feb4593 100644 --- a/config/packages/workflow.yaml +++ b/config/packages/workflow.yaml @@ -15,6 +15,7 @@ framework: - ended - checking_invoice - completed + - called_off transitions: upload_contract: from: new diff --git a/migrations/Version20250722125647.php b/migrations/Version20250722125647.php new file mode 100644 index 0000000..53f45aa --- /dev/null +++ b/migrations/Version20250722125647.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE disposition ADD called_off_by VARCHAR(64) DEFAULT NULL, ADD called_off_reason LONGTEXT DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE disposition DROP called_off_by, DROP called_off_reason'); + } +} diff --git a/src/Controller/Administrative/Disposition/CallOffController.php b/src/Controller/Administrative/Disposition/CallOffController.php new file mode 100644 index 0000000..2899ca6 --- /dev/null +++ b/src/Controller/Administrative/Disposition/CallOffController.php @@ -0,0 +1,66 @@ +createForm(DispositionCallOffType::class, $disposition, ['hx_post' => $request->getUri()]); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $disposition->setStatus(Assignment::STATUS_CALLED_OFF); + $sendNotification = $form->get('sendNotification')->getData(); + + $this->entityManager->flush(); + + $this + ->eventDispatcher + ->dispatch(new DispositionCalledOffEvent($disposition, $sendNotification), DispositionCalledOffEvent::NAME) + ; + + $assignment = $disposition->getAssignment(); + + $this->logger->info('Call off disposition', [ + 'assignment_id' => $disposition->getId(), + 'destination' => (string) $assignment->getDestination(), + 'job_profile' => $assignment->getJobProfile()->getName(), + ]); + + $this->addFlash('success', 'Der Einsatz wurde abgesagt'); + + return new HxRedirectResponse($this->generateUrl('app_administrative_assignment_detail', [ + 'uuid' => $assignment->getUuid(), + ])); + } + + return $this->render('administrative/disposition/modal_call_off.html.twig', [ + 'disposition' => $disposition, + 'form' => $form->createView(), + ]); + } +} diff --git a/src/Entity/Assignment.php b/src/Entity/Assignment.php index 3a8cbfb..92e8562 100644 --- a/src/Entity/Assignment.php +++ b/src/Entity/Assignment.php @@ -487,6 +487,13 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa }); } + public function getActiveDispositions(): Collection + { + return $this->dispositions->filter(function (Disposition $disposition) { + return Disposition::STATUS_CALLED_OFF !== $disposition->getStatus(); + }); + } + public function getConfirmedDispositions(): Collection { return $this->dispositions->filter(function (Disposition $disposition) { diff --git a/src/Entity/Disposition.php b/src/Entity/Disposition.php index 04320d0..7abff61 100644 --- a/src/Entity/Disposition.php +++ b/src/Entity/Disposition.php @@ -25,6 +25,10 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf public const STATUS_CHECKING_INVOICE = 'checking_invoice'; public const STATUS_PAID = 'paid'; public const STATUS_COMPLETED = 'completed'; + public const STATUS_CALLED_OFF = 'called_off'; + + public const CALLED_OFF_BY_TEAMER = 'teamer'; + public const CALLED_OFF_BY_OFFICE = 'office'; #[ORM\Id] #[ORM\GeneratedValue] @@ -59,6 +63,12 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf #[ORM\JoinColumn(onDelete: 'SET NULL')] private ?Feedback $feedback = null; + #[ORM\Column(length: 64, nullable: true)] + private ?string $calledOffBy = null; + + #[ORM\Column(type: Types::TEXT, nullable: true)] + private ?string $calledOffReason = null; + public function __construct(Application $application) { $this->uuid = Uuid::v4(); @@ -243,4 +253,28 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf return $period->isStarted(); } + + public function getCalledOffBy(): ?string + { + return $this->calledOffBy; + } + + public function setCalledOffBy(?string $calledOffBy): static + { + $this->calledOffBy = $calledOffBy; + + return $this; + } + + public function getCalledOffReason(): ?string + { + return $this->calledOffReason; + } + + public function setCalledOffReason(?string $calledOffReason): static + { + $this->calledOffReason = $calledOffReason; + + return $this; + } } diff --git a/src/Event/DispositionCalledOffEvent.php b/src/Event/DispositionCalledOffEvent.php new file mode 100644 index 0000000..ab2f9c8 --- /dev/null +++ b/src/Event/DispositionCalledOffEvent.php @@ -0,0 +1,25 @@ +disposition; + } + + public function isSendNotification(): bool + { + return $this->sendNotification; + } +} diff --git a/src/EventListener/EmailNotificationSubscriber.php b/src/EventListener/EmailNotificationSubscriber.php index f371593..d278f1a 100644 --- a/src/EventListener/EmailNotificationSubscriber.php +++ b/src/EventListener/EmailNotificationSubscriber.php @@ -7,6 +7,7 @@ use App\Entity\Application; use App\Entity\Upload; use App\Event\ApplicationStatusEvent; use App\Event\AssignmentCalledOffEvent; +use App\Event\DispositionCalledOffEvent; use App\Event\DispositionCreatedEvent; use App\Event\DocumentConfirmedEvent; use App\Event\DocumentRejectedEvent; @@ -21,7 +22,7 @@ class EmailNotificationSubscriber implements EventSubscriberInterface public function __construct( private readonly Mailer $mailer, private readonly UserRepository $userRepository, - private readonly ContractRenderer $contractRenderer + private readonly ContractRenderer $contractRenderer, ) { } @@ -29,6 +30,7 @@ class EmailNotificationSubscriber implements EventSubscriberInterface { return [ DispositionCreatedEvent::NAME => 'onDispositionCreated', + DispositionCalledOffEvent::NAME => 'onDispositionCalledOff', DocumentUploadedEvent::NAME => 'onDocumentUploaded', DocumentRejectedEvent::NAME => 'onDocumentRejected', ApplicationStatusEvent::NAME => 'onApplicationStatus', @@ -38,7 +40,7 @@ class EmailNotificationSubscriber implements EventSubscriberInterface } /** - * Notify teamer about accepted application + * Notify teamer about accepted application. */ public function onDispositionCreated(DispositionCreatedEvent $event): void { @@ -97,7 +99,7 @@ class EmailNotificationSubscriber implements EventSubscriberInterface } /** - * Notify managers about uploaded documents + * Notify managers about uploaded documents. */ public function onDocumentUploaded(DocumentUploadedEvent $event): void { @@ -142,7 +144,7 @@ class EmailNotificationSubscriber implements EventSubscriberInterface } /** - * Notify teamer about rejected document + * Notify teamer about rejected document. */ public function onDocumentRejected(DocumentRejectedEvent $event): void { @@ -168,7 +170,7 @@ class EmailNotificationSubscriber implements EventSubscriberInterface } /** - * Notify teamer about rejected application + * Notify teamer about rejected application. */ public function onApplicationStatus(ApplicationStatusEvent $event): void { @@ -189,6 +191,31 @@ class EmailNotificationSubscriber implements EventSubscriberInterface ]); } + /** + * Notify teamer about called-off disposition (if applicable). + */ + public function onDispositionCalledOff(DispositionCalledOffEvent $event): void + { + if (false === $event->isSendNotification()) { + return; + } + + $disposition = $event->getDisposition(); + $teamer = $disposition->getTeamer(); + + $this->mailer->createAndSendEmail([ + 'assignment' => $disposition->getAssignment(), + 'reason' => $disposition->getCalledOffReason(), + ], [ + 'to' => $teamer->getCommunication()->getEmail(), + 'subject' => 'Dein Einsatz wurde abgesagt', + 'template' => 'email/disposition_called_off.html.twig', + ]); + } + + /** + * Notify all assigned teamers about called-off assignment. + */ public function onAssignmentCalledOff(AssignmentCalledOffEvent $event): void { $assignment = $event->getAssignment(); @@ -204,4 +231,4 @@ class EmailNotificationSubscriber implements EventSubscriberInterface ]); } } -} \ No newline at end of file +} diff --git a/src/EventListener/StaffingStatusSubscriber.php b/src/EventListener/StaffingStatusSubscriber.php index 796814a..f35e317 100644 --- a/src/EventListener/StaffingStatusSubscriber.php +++ b/src/EventListener/StaffingStatusSubscriber.php @@ -7,6 +7,7 @@ use App\Entity\Upload; use App\Event\ApplicationCreatedEvent; use App\Event\ApplicationDeletedEvent; use App\Event\ApplicationStatusEvent; +use App\Event\DispositionCalledOffEvent; use App\Event\DispositionCreatedEvent; use App\Event\DispositionDeletedEvent; use App\Event\DocumentConfirmedEvent; @@ -27,6 +28,7 @@ class StaffingStatusSubscriber implements EventSubscriberInterface ApplicationStatusEvent::NAME => 'onApplicationStatusUpdated', DispositionCreatedEvent::NAME => 'onDispositionCreated', DispositionDeletedEvent::NAME => 'onDispositionDeleted', + DispositionCalledOffEvent::NAME => 'onDispositionCalledOff', DocumentConfirmedEvent::NAME => 'onDocumentConfirmed', ]; } @@ -67,6 +69,13 @@ class StaffingStatusSubscriber implements EventSubscriberInterface $this->updateStaffingStatus($assignment); } + public function onDispositionCalledOff(DispositionCalledOffEvent $event): void + { + $assignment = $event->getDisposition()->getAssignment(); + + $this->updateStaffingStatus($assignment); + } + public function onDocumentConfirmed(DocumentConfirmedEvent $event): void { $document = $event->getDocument(); @@ -87,4 +96,4 @@ class StaffingStatusSubscriber implements EventSubscriberInterface $this->entityManager->flush(); } -} \ No newline at end of file +} diff --git a/src/Form/DispositionCallOffType.php b/src/Form/DispositionCallOffType.php new file mode 100644 index 0000000..dbfa138 --- /dev/null +++ b/src/Form/DispositionCallOffType.php @@ -0,0 +1,52 @@ +add('calledOffBy', ChoiceType::class, [ + 'label' => 'abgesagt durch', + 'choices' => [ + 'Teamer:in' => Disposition::CALLED_OFF_BY_TEAMER, + 'E&P' => Disposition::CALLED_OFF_BY_OFFICE, + ], + ]) + ->add('calledOffReason', TextareaType::class, [ + 'label' => 'Begründung', + 'attr' => [ + 'data-controller' => 'textarea-autosize', + 'data-action' => 'textarea-autosize#resize', + ], + 'constraints' => [ + new NotBlank([ + 'message' => 'Bitte gib die Begründung ein', + ]), + ], + ]) + ->add('sendNotification', CheckboxType::class, [ + 'label' => 'Benachrichtigung mit Begründung versenden', + 'required' => false, + 'mapped' => false, + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Disposition::class, + ]); + } +} diff --git a/src/Security/Voter/DispositionVoter.php b/src/Security/Voter/DispositionVoter.php index b86b937..4ac1f3e 100644 --- a/src/Security/Voter/DispositionVoter.php +++ b/src/Security/Voter/DispositionVoter.php @@ -18,6 +18,7 @@ class DispositionVoter extends Voter public const CONTRACT_SUPPLEMENTARY = 'CONTRACT_SUPPLEMENTARY'; public const INVOICE = 'INVOICE'; public const FEEDBACK = 'FEEDBACK'; + public const CALL_OFF = 'CALL_OFF'; public function __construct(private readonly Security $security) { @@ -37,6 +38,7 @@ class DispositionVoter extends Voter static::CONTRACT_SUPPLEMENTARY, static::INVOICE, static::FEEDBACK, + static::CALL_OFF, ]); } @@ -53,6 +55,8 @@ class DispositionVoter extends Voter static::FEEDBACK => $this->security->isGranted('ROLE_ADMINISTRATIVE') || $this->assertHouseManagerAccess($token, $disposition), static::CONTRACT_SUPPLEMENTARY => $this->assertContractUploadAllowed($disposition), + static::CALL_OFF => $this->security->isGranted('ROLE_ADMINISTRATIVE') + && Disposition::STATUS_CALLED_OFF !== $disposition->getStatus(), default => false, }; } @@ -63,6 +67,10 @@ class DispositionVoter extends Voter return false; } + if (Disposition::STATUS_CALLED_OFF === $disposition->getStatus()) { + return false; + } + /** @var User $user */ $user = $token->getUser(); @@ -95,6 +103,10 @@ class DispositionVoter extends Voter return false; } + if (Disposition::STATUS_CALLED_OFF === $disposition->getStatus()) { + return false; + } + return null === $disposition->getDocumentByType(Upload::TYPE_CONTRACT); } } diff --git a/symfony.lock b/symfony.lock index 23e1bf5..1492670 100644 --- a/symfony.lock +++ b/symfony.lock @@ -47,6 +47,18 @@ "config/packages/flagception.yaml" ] }, + "friendsofphp/php-cs-fixer": { + "version": "3.84", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.0", + "ref": "be2103eb4a20942e28a6dd87736669b757132435" + }, + "files": [ + ".php-cs-fixer.dist.php" + ] + }, "knplabs/knp-menu-bundle": { "version": "v3.2.0" }, diff --git a/templates/administrative/assignment/detail.html.twig b/templates/administrative/assignment/detail.html.twig index 657382b..4723ca9 100644 --- a/templates/administrative/assignment/detail.html.twig +++ b/templates/administrative/assignment/detail.html.twig @@ -170,7 +170,11 @@ {% endif %} - {{ disposition.remarks|default('-')|nl2br }} + {% if disposition.calledOffReason %} + {{ disposition.calledOffReason|default('-')|nl2br }} + {% else %} + {{ disposition.remarks|default('-')|nl2br }} + {% endif %}
@@ -197,6 +201,16 @@ {{ icon('upload') }} {% endif %} + {% if is_granted('CALL_OFF', disposition) %} + + {% endif %} {% if is_granted('DELETE', disposition) %}
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/administrative/assignment/index.html.twig b/templates/administrative/assignment/index.html.twig index 5b93dc1..d9a3844 100644 --- a/templates/administrative/assignment/index.html.twig +++ b/templates/administrative/assignment/index.html.twig @@ -139,9 +139,9 @@ {% endif %} - {{ assignment.dispositions|length }}/{{ assignment.availableDispositions }} + {{ assignment.activeDispositions|length }}/{{ assignment.availableDispositions }}